【解题报告】13级个人结业赛(二) ——动(dou)态(bu)规(hui)划(zuo)专场
额。果然是动(dou)态(bu)规(hui)划(zuo)专场。。。
A: 翻倍序列
dp[i][j]表示第i个位置是j的情况的个数
那么dp[i][j]=∑dp[i-1][k] (j%k==0)
初始状态下dp[0][j]=1。(1<=j<=n)
最后要求的答案是∑dp[n-1][i] (1<=i<=n)
可以先预处理好所有答案,然后询问的时候直接求和输出。
1 #include<stdio.h> 2 #include<math.h> 3 #include<string.h> 4 #include<iostream> 5 #include<algorithm> 6 #include<map> 7 #include<set> 8 #include<queue> 9 #include<stack> 10 #define FOR(i,n) for(i=0;i<(n);i++) 11 #define CLR(a) memset(a,0,sizeof(a)) 12 #define CIN(a) scanf("%d",&a) 13 typedef long long ll; 14 using namespace std; 15 int dp[2005][2005]; 16 const int MOD=1000000007; 17 int main() 18 { 19 int n,k,i,j,kk; 20 n=2000,k=2000; 21 for(j=1;j<=n;j++) dp[0][j]=1; 22 for(i=0;i<k-1;i++){ 23 for(j=1;j<=n;j++){ 24 for(kk=j;kk<=n;kk+=j){ 25 dp[i+1][kk]=(dp[i+1][kk]+dp[i][j])%MOD; 26 } 27 } 28 } 29 while(scanf("%d%d",&n,&k)!=EOF) { 30 int ans=0; 31 for(j=1;j<=n;j++){ 32 ans=(ans+dp[k-1][j])%MOD; 33 } 34 printf("%d\n",ans); 35 } 36 return 0; 37 }
B: 汉诺塔
dp[k][i][j]表示把k个盘子从i移动到j所需要的最小花费。
首先z=3-i-j,表示除了i和j的另一个柱子
那么移动的时候有2种情况:
1.先把k-1个从i移动到z,然后把最大的那个从i移动到j,最后把z-1个从k移动到j
也就是dp[k][i][j]=dp[k-1][i][z]+cost[i][j]+dp[k-1][z][j]
2.先把k-1个从i移动到j,然后把最大的那个从i移动到z,之后把k-1个从j移动回i,再把最大的从z移动到j,最后k-1个从i移动到j
也就是dp[k][i][j]=dp[k-1][i][j]+cost[i][z]+dp[k-1][j][i]+cost[z][j]+dp[k-1][i][j]
然后两者取较小值。
最后要注意的是dp[0][i][j]=min{cost[i][j] , cost[i][z]+cost[z][j]}
1 #include<stdio.h> 2 #include<math.h> 3 #include<string.h> 4 #include<iostream> 5 #include<algorithm> 6 #include<map> 7 #include<set> 8 #include<queue> 9 #include<stack> 10 #define FOR(i,n) for(i=0;i<(n);i++) 11 #define CLR(a) memset(a,0,sizeof(a)) 12 #define CIN(a) scanf("%d",&a) 13 typedef long long ll; 14 using namespace std; 15 int cost[3][3]; 16 ll DP[1000][3][3]; 17 ll dfs(int n,int i,int j){ 18 //printf("%d %d %d\n",n,i,j); 19 int k=3-i-j; 20 if(n==1) return min(cost[i][j],cost[i][k]+cost[k][j]); 21 if(DP[n][i][j]!=-1){ 22 return DP[n][i][j]; 23 } 24 ll ans1=0; 25 ans1=dfs(n-1,i,k); 26 ans1+=cost[i][j]; 27 ans1+=dfs(n-1,k,j); 28 29 ll ans2=0; 30 ans2=dfs(n-1,i,j); 31 ans2+=cost[i][k]; 32 ans2+=dfs(n-1,j,i); 33 ans2+=cost[k][j]; 34 ans2+=dfs(n-1,i,j); 35 36 return DP[n][i][j]=min(ans1,ans2); 37 } 38 int main() 39 { 40 int n; 41 while(scanf("%d%d%d",&cost[0][0],&cost[0][1],&cost[0][2])!=EOF) 42 { 43 memset(DP,-1,sizeof(DP)); 44 scanf("%d%d%d",&cost[1][0],&cost[1][1],&cost[1][2]); 45 scanf("%d%d%d",&cost[2][0],&cost[2][1],&cost[2][2]); 46 scanf("%d",&n); 47 printf("%lld\n",dfs(n,0,2)); 48 } 49 return 0; 50 }
C: 红黑树
dp[i][0]表示有i个节点的树,如果根节点是黑色,有几种构造。
dp[i][1]表示有i个节点的树,如果根节点是红色,有几种构造。
根据题意可知,最后要求的就是dp[n][0]的值(根节点必须是是黑色)
dp[i][0]= dp[i/2+i%2][0]*dp[i/2][0]
+dp[i/2+i%2][0]*dp[i/2][1]
+dp[i/2+i%2][1]*dp[i/2][0]
+dp[i/2+i%2][1]*dp[i/2][1]
dp[i][1]= dp[i/2+i%2][0]*dp[i/2][0]
dp[1][0]=1
dp[1][1]=0 (叶子节点不能是红色)
dp[2][1]=dp[2][0]=1
然后递推就行了。。
1 #include<stdio.h> 2 #include<math.h> 3 #include<string.h> 4 #include<iostream> 5 #include<algorithm> 6 #include<map> 7 #include<set> 8 #include<queue> 9 #include<stack> 10 #define FOR(i,n) for(i=0;i<(n);i++) 11 #define CLR(a) memset(a,0,sizeof(a)) 12 #define CIN(a) scanf("%d",&a) 13 typedef long long ll; 14 using namespace std; 15 const ll MOD=1000000007; 16 ll DP[1000005][2]; 17 ll dfs(int x,int c)//x个节点,根节点颜色是c(0黑 1红) 18 { 19 //if(x<0) return 0; 20 if(x==1&&c==1) return 0;//叶子节点不能是红色 21 if(x==1) return 1;//叶子节点是黑色 22 if(DP[x][c]!=0) return DP[x][c]; 23 ll ret=0,l,r; 24 if((x-1)%2) l=(x-1)/2+1; 25 else l=(x-1)/2; 26 r=(x-1)/2; 27 if(!c){//黑色 28 if(r!=0){ 29 ret=(ret+(dfs(l,0)*dfs(r,1))%MOD)%MOD; 30 ret=(ret+(dfs(l,1)*dfs(r,0))%MOD)%MOD; 31 ret=(ret+(dfs(l,1)*dfs(r,1))%MOD)%MOD; 32 ret=(ret+dfs(l,0)*dfs(r,0)%MOD)%MOD; 33 }else{ret=1;} 34 }else{ 35 if(r!=0){ 36 ret=(ret+dfs(l,0)*dfs(r,0)%MOD)%MOD; 37 }else{ret=1;} 38 } 39 return DP[x][c]=ret; 40 } 41 int main() 42 { 43 int n; 44 memset(DP,0,sizeof(DP)); 45 while(scanf("%d",&n)!=EOF){ 46 printf("%lld\n",dfs(n,0)); 47 } 48 return 0; 49 }
D: 01序列
dp[i][0]表示前i个字符组成的的子序列中 以0为结尾的01序列
dp[i][1]表示前i个字符组成的的子序列中 以1为结尾的01序列
dp[i][0]=dp[i-1][1]+1 dp[i][1]=dp[i-1][1] (s[i]==0)
dp[i][1]=dp[i-1][0]+1 dp[i][0]=dp[i-1][0] (s[i]==1)
把全部加起来就是答案了。
1 #include<stdio.h> 2 #include<math.h> 3 #include<string.h> 4 #include<iostream> 5 #include<algorithm> 6 #include<map> 7 #include<set> 8 #include<queue> 9 #include<stack> 10 #define FOR(i,n) for(i=0;i<(n);i++) 11 #define CLR(a) memset(a,0,sizeof(a)) 12 #define CIN(a) scanf("%d",&a) 13 typedef long long ll; 14 using namespace std; 15 int main() 16 { 17 int n,i; 18 int MOD=1000000007; 19 while(scanf("%d",&n)!=EOF){ 20 ll ans=0; 21 ll a0=0,a1=0; 22 for(i=0;i<n;i++){ 23 if(i%2){ 24 ans=(ans+(a0+1))%MOD; 25 a1=(a1+a0+1)%MOD; 26 }else{ 27 ans=(ans+(a1+1))%MOD; 28 a0=(a0+a1+1)%MOD; 29 } 30 } 31 printf("%lld\n",ans); 32 } 33 return 0; 34 }
E: 矩阵的最长不降子串
dp[i][j]表示以a[i][j]结尾的不递减子串最长的长度
那么dp[i][j]=max{
if(a[i][j]>=a[i-1][j]) dp[i-1][j]+1
if(a[i][j]>=a[i][j-1]) dp[i][j-1]+1
}
最后答案是最大的dp[i][j]
1 #include<stdio.h> 2 #include<math.h> 3 #include<string.h> 4 #include<iostream> 5 #include<algorithm> 6 #include<map> 7 #include<set> 8 #include<queue> 9 #include<stack> 10 #define FOR(i,n) for(i=0;i<(n);i++) 11 #define CLR(a) memset(a,0,sizeof(a)) 12 #define CIN(a) scanf("%d",&a) 13 typedef long long ll; 14 using namespace std; 15 int dp[1005][1005]; 16 int a[1005][1005]; 17 int n,m; 18 int main() 19 { 20 int i,j; 21 while(scanf("%d%d",&n,&m)!=EOF){ 22 int ans=1; 23 for(i=0;i<n;i++){ 24 for(j=0;j<m;j++){ 25 scanf("%d",&a[i][j]); 26 dp[i][j]=1; 27 if(i!=0&&a[i-1][j]<=a[i][j]){ 28 dp[i][j]=max(dp[i][j],dp[i-1][j]+1); 29 } 30 if(j!=0&&a[i][j-1]<=a[i][j]){ 31 dp[i][j]=max(dp[i][j],dp[i][j-1]+1); 32 } 33 ans=max(ans,dp[i][j]); 34 } 35 } 36 printf("%d\n",ans); 37 } 38 return 0; 39 }