noi 2009 管道取珠 动态规划
这是一道非常精致的递推问题,程序非常短,但很难想到。问题的关键是对目标的转化以及递推状态的设计。
首先对 ∑ai^2 进行转化,将其这样理解:用A和B分别表示一种取珠的方法,将结果相同的两种取珠方法(不管这两种取珠方法本身是否相同)记为(A, B),不难发现∑ai^2就是所有这样的(A, B)的对数。
设状态f(a1, b1, a2, b2),如果f(a1, b1, a2, b2) = K,表示存在K对不完全相同的(A, B),使得A方法已经取出了第一个串的前a1个字符及第二个串的前b1个字符,B方法已经取出了第一个串的前a2个字符以及第二个串的前b2个字符,同时A方法与B方法得到的结果相同。
显然,f(a1, b1, a2, b2)可以转移向4个方向:f(a1+1, b1, a2+1, b2),f(a1+1, b1, a2, b2+1),f(a1, b1+1, a2+1, b2),f(a1, b1+1, a2, b2+1),转移可行的前提是对应的字母相同。
最后,f(n, m, n, m)就是问题的答案。递推初态是f(0, 0, 0, 0) = 1.
另外,由于A和B的结果相同,显然有a1+b1 = a2+b2,故只要存储其中的三个值即可。算法的时空复杂度均为O(n3)。空间复杂度经优化可到n^2
转自http://hi.baidu.com/19930705cxjff/blog/item/a1dd92deb7872454ccbf1a82.html
1 #include<iostream> 2 #include<cmath> 3 #include<cstring> 4 #include<cstdio> 5 using namespace std; 6 #define MAXN 501 7 #define MOD 1024523 8 int dp[2][MAXN][MAXN]; 9 int n,m; 10 char c1[MAXN],c2[MAXN]; 11 int main() 12 { 13 memset(dp,0,sizeof(dp)); 14 scanf("%d%d",&n,&m); 15 int i,j,k; 16 scanf("%s",c1); 17 scanf("%s",c2); 18 dp[0][0][0]=1; 19 for(k=0;k<n+m;k++) 20 for(i=0;i<=min(k,n);i++) 21 for(j=0;j<=min(k,n);j++) 22 { 23 if(i<n&&j<n&&c1[n-i-1]==c1[n-j-1]) 24 dp[(k+1)%2][i+1][j+1]=(dp[k%2][i][j]+dp[(k+1)%2][i+1][j+1])%MOD; 25 if(i<n&&k-j<m&&c1[n-i-1]==c2[m-k+j-1]) 26 dp[(k+1)%2][i+1][j]=(dp[k%2][i][j]+dp[(k+1)%2][i+1][j])%MOD; 27 if(k-i<m&&j<n&&c2[m-k+i-1]==c1[n-j-1]) 28 dp[(k+1)%2][i][j+1]=(dp[k%2][i][j]+dp[(k+1)%2][i][j+1])%MOD; 29 if(k-i<m&&k-j<m&&c2[m-k+i-1]==c2[m-k+j-1]) 30 dp[(k+1)%2][i][j]=(dp[k%2][i][j]+dp[(k+1)%2][i][j])%MOD; 31 //cout<<k<<" "<<i<<" "<<j<<" "<<dp[k%2][i][j]<<endl; 32 dp[k%2][i][j]=0; 33 } 34 printf("%d\n",dp[(n+m)%2][n][n]); 35 return 0; 36 }