[ABC200E] Patisserie ABC 2

前言

你说为什么一定要想 \(O(n\log_2n)\) 或者数学做法直接算呢?OI 血统的 \(\tt dp\) 不香吗?

题目

AtCoder

讲解

我们如果知道了三种值的和,以及其对应的前缀方案数,那么就可以很轻松求出答案了。

考虑 \(\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;
}
posted @ 2021-05-12 21:26  皮皮刘  阅读(132)  评论(0编辑  收藏  举报