2021湖南多校对抗赛第二场 B

题目链接:

https://nanti.jisuanke.com/t/41007

Solution:

挺不错的一道dp。

不难发现砖块顺序不影响结果,所以直接记录下每个位置有多少个砖块落下,从左往右顺序处理即可

这道题很重要的一点是要想到去维护空位而不是维护有砖块的位置

(dp[i])表示考虑令第(i)位为空,并且处理完前(i)位所有砖块的方案数,(f[i])处理完前(i)个位置后,产生了(i)个空位的方案数,(a[i])表示前(i)个位置砖块数的前缀和

若这个位置有砖块落下,跳过不处理就是了(也就是(dp[i]=0)

考虑若这个位置没有砖块落下((a[i]=a[i-1])),且有(i-1-a[i-1]>=0)(也就是这个位置存在作为空位的合法性),则有状态转移方程:

[dp[i]=f[i-1-a[i-1]],f[i-a[i]]+=dp[i] ]

另外,之前我一直没想明白这个dp方程的正确性。因为(f[i])由于是用加法更新的,实际上是包含了之前每一次决策后产生(i)个空位的方案总和,而(dp[i])则应该只用到上一次的决策的结果啊。

换句话说,对于位置(j),我刚处理完了(j)前面的一个位置(k),此时产生了(i)个空位的方案数为(f[i]),然而之后处理位置(j)时,在(j)(k)的过程中我在你(k)位置得到的(f[i])里的那些方案的基础上一通乱铺,就不能保证那些原本截止到(k)位置(即(1)(k))有(i)个空位的方案到(j)后仍然有(i)个空位啊。

然而我此时(f[i])却包含了那些方案,若用(f[i])去更新此时的(dp[j]),不就出问题了吗?

后来想想,如果我处理(j)时又调用到了这个(f[i]),显然,有(k-a[k]=j-1-a[j-1]=i),也就是(j-1-k=a[j-1]-a[k]),即((k,j))这段区间上一定有(j-k-1)个砖落下,又因为(k)(j)都不能放砖,我只能选择用这些砖把((k,j))这段区间铺满,不会有新空位产生,这样一来这个看起来有问题的(f[i])就是正确的了。

换言之,对于某个(f[i]),在转移过程中可能不是正确的,但如果满足调用到他的条件时,他就恰好是正确的了。这我认为是这个dp方程最巧妙的点

扯得有点多了,自己还是fw了......

#include<bits/stdc++.h>
using namespace std;
const int M=1e9+7;
int n,m;
int dp[1000005],f[1000005],a[1000005];
int main()
{
	memset(dp,0,sizeof(dp));
	memset(f,0,sizeof(f));
	memset(a,0,sizeof(a));
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++)
	{
		int x;
		scanf("%d",&x);
		a[x]++;
	}
	for(int i=1;i<=n+1;i++)
		a[i]+=a[i-1];
	f[0]=1;	
	for(int i=1;i<=n+1;i++)
	{
		if(a[i]==a[i-1])
		{
			if(i-1-a[i-1]>=0) dp[i]=f[i-1-a[i-1]];
			if(i-a[i]>=0) f[i-a[i]]=(f[i-a[i]]+dp[i])%M;
		}
	}
	printf("%d",dp[n+1]);	
	return 0;
} 
小鳥の翼がついに大きくなって , 旅立ちの日だよ , 遠くへと広がる海の色暖かく , 夢の中で描いた絵のようなんだ , 切なくて時をまきもどしてみるかい ? No no no いまが最高! だってだって、いまが最高!
原文地址:https://www.cnblogs.com/nanjolno/p/14608648.html