[NOI2009] 管道取珠

link

也是一道颇具技巧性的题目。果然DP具有毫无章法、稀奇古怪的特点。就这道题来说,看到题解的思路之后整个人都不好了。

主要的思维难点在于转化它的求解内容,即 \(\sum{a_i^2}\) 。看到这个式子以后我以为就是一个普通的贡献方程,结果不曾想它竟然还有实际含义。就像画树状图一样,某种情况数的平方可以看成是两个人选择相同方案的数目。就很离谱。太神仙了。

明白了这一点就很简单了。\(N\le 500\) 的数据量支持 \(O(N^3)\) 的算法,直接二维DP即可。用 f[i][j][k]来记录第一个人取了上j下i-j,第二个人取了上k下i-k时两个人取得相同序列的方案数。显然每个人上一次要么取上面的,要么去下面的,决策数很少,直接枚举即可。滚动数组卡卡常就可以过了。

#include<cstdio>
//#define zczc
const int N=510;
const int mod=1024523;
inline void read(int &wh){
    wh=0;int f=1;char w=getchar();
    while(w<'0'||w>'9'){if(w=='-')f=-1;w=getchar();}
    while(w<='9'&&w>='0'){wh=wh*10+w-'0';w=getchar();}
    wh*=f;return;
}
inline bool get(){
	char w=getchar();
	while(w!='A'&&w!='B')w=getchar();
	return w=='A';
}

int m,n,a[N],b[N],f[2][N][N];

signed main(){
	
	#ifdef zczc
	freopen("in.txt","r",stdin);
	#endif
	
	read(m);read(n);
	for(int i=m;i;i--)a[i]=get();
	for(int i=n;i;i--)b[i]=get();
	f[0][0][0]=1;
	for(int i=1;i<=m+n;i++){
		int now=i&1,last=i+1&1,bg=i-n<0?0:i-n;
		for(int j=bg;j<=i&&j<=m;j++){
			for(int k=bg;k<=i&&k<=m;k++){
				f[now][j][k]=0;
				if(a[j]==a[k]&&j>0&&k>0)
					f[now][j][k]+=f[last][j-1][k-1];
				if(a[j]==b[i-k]&&j>0&&i-k>0)
					f[now][j][k]+=f[last][j-1][k];
				if(b[i-j]==a[k]&&i-j>0&&k>0)
					f[now][j][k]+=f[last][j][k-1];
				if(b[i-j]==b[i-k]&&i-j>0&&i-k>0)
					f[now][j][k]+=f[last][j][k];
				f[now][j][k]%=mod;
			}
		}
	}
	printf("%d\n",f[m+n&1][m][m]);
	
	return 0;
}
posted @ 2022-05-17 22:25  Feyn618  阅读(26)  评论(0编辑  收藏  举报