前言
你说为什么一定要想 \(O(n\log_2n)\) 或者数学做法直接算呢?OI 血统的 \(\tt dp\) 不香吗?
题目
讲解
我们如果知道了三种值的和,以及其对应的前缀方案数,那么就可以很轻松求出答案了。
考虑 \(\tt dp\)。这是一个类似于背包的过程,虽然跟背包好像差的有点远。
令 \(dp_{i,j}\) 表示 \(i\) 个数总和为 \(j\),每个数不超过 \(n\) 的方案数。其实知道了用 \(\tt dp\) 以及状态之后,转移并不困难,具体可以参见代码。
注意我们求的是前缀和形式。
时间复杂度 \(O(n)\)。
代码
int main()
{
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
n = Read(); k = Read();
for(int i = 1;i <= n;++ i) dp[0][i] = i;//这个是前缀和
for(int i = 2;i <= 2*n;++ i) dp[1][i] = dp[0][Min(i-1ll,n)] - dp[0][Max(i-n-1,0ll)],dp[1][i] += dp[1][i-1];
for(int i = 3;i <= 3*n;++ i) dp[2][i] = dp[1][Min(i-1ll,2*n)] - dp[1][Max(i-n-1,0ll)],dp[2][i] += dp[2][i-1];
for(int S = 3;S <= 3*n;++ S)
{
if(dp[2][S] >= k)
{
k -= dp[2][S-1];
for(int i = 1;i <= n;++ i)
if(S-i >= 2 && S-i <= 2*n)
{
if(dp[1][S-i]-dp[1][S-i-1] >= k)
{
for(int j = 1;j <= n;++ j)
if(S-i-j >= 1 && S-i-j <= n)
{
if(dp[0][S-i-j]-dp[0][S-i-j-1] >= k)
{
Put(i,' '),Put(j,' '),Put(S-i-j);
return 0;
}
else k -= dp[0][S-i-j]-dp[0][S-i-j-1];
}
}
else k -= dp[1][S-i] - dp[1][S-i-1];
}
}
}
return 0;
}