Dp刷(chao)题记录&题(fu)解(zhi)
1.bzoj1055
[HAOI2008]玩具取名
题目大意:字典中有四个字母,’w’\’i’\’n’\’g’,给出每个字母的转换关系,即某个单个字母可以转换为两个字母。给出一个文本,求最初的可能文本(刚开始有且仅有一个字母)。
题解:明显是一道区间dp嘛~。设状态为文本[i,j]内的字母可以转化为字母[k],即f(i,j,k),要解状态的可能性。转移思路,自然是枚举i到j内的断点,再枚举关系。那么初始的状态转移方程就是
f[i][j][k]=f[i][x][pair(a)]==true
And f[x+1][j][pair(b)]==true ,其中pair(a)和pair(b)可以组合成k,而这个思路的时间复杂度是O(4
× len^3 × relation_number)。
//Kaiba_Seto 20170118 //orz cjkmao #include <stdio.h> #include <string.h> #define RG register #define __inline__ __attribute__((always_inline)) int n[5],t[5][17][3],len,f[205][205][5]; char ch[205],o[5]={‘W‘,‘I‘,‘N‘,‘G‘}; int rec(RG char c) { if(c==‘W‘)return 0; if(c==‘I‘)return 1; if(c==‘N‘)return 2; return 3; } bool dfs(RG int x,RG int y,RG int k) { if(x == y)return ch[x] == o[k]; if(~f[x][y][k])return f[x][y][k]; for(RG int i=1;i<=n[k];i++) for(RG int j=x; j<y; j++) if(dfs(x,j,t[k][i][0]) && dfs(j+1,y,t[k][i][1])) return f[x][y][k]=1; return f[x][y][k]=0; } int main() { memset(f,-1,sizeof(f)); for(RG int i=0; i<4; i++) scanf("%d",&n[i]); for(RG int i=0; i<4; i++) for(RG int j=1; j<=n[i]; j++) { scanf("%s",ch); t[i][j][0]=rec(ch[0]); t[i][j][1]=rec(ch[1]); } scanf("%s",ch+1); len=strlen(ch+1); RG bool advanced=false; for(RG int k=0; k<4; k++) if(dfs(1,len,k)) advanced=true, printf("%c",o[k]); puts(advanced?"":"The name is wrong!"); return 0; }
2.bzoj1261 [SCOI2006]zh_tree
题目大意:张老师有一篇论文,有n个指标,每个指标在论文中出现的次数与论文指标出现总次数的比值成为指标的概率/频率,对指标序列1~n构建一颗树,令每个节点深度为r,规定根节点有r=0,记h=k(r+1)+c为访问点的代价,而f1*h1+f2*h2+...+fn*hn为树的平均代价,求树的最小平均代价。(题目给出的1~n的序列即为树的中序遍历)
题解:因为给定的序列是树的中序遍历,我们可以利用这个性质,树的问题转化为区间dp。令f[i][j]为区间[i,j]的最小代价,那么我们每次对一个区间选一个根节点,把左右区间分为左右子树就行,最后递归求解即可,深度处理的话,每层累计用前缀和优化一下就行了。
#include <stdio.h> #include <string.h> #define RG register #define inf 1e6 #define dmin(a,b) ((a) < (b) ? (a) : (b)) int n; bool vis[31][31]; double f[31][31],a[31],k,c; double dfs(RG int x,RG int y) { if(x > y)return 0; if(vis[x][y])return f[x][y]; f[x][y]=inf; vis[x][y]=true; for(RG int i=x; i<=y; i++) f[x][y]=dmin(f[x][y],dfs(x,i-1)+dfs(i+1,y)+c* (a[i]-a[i-1])/a[n]); return f[x][y]+=k*(a[y]-a[x-1])/a[n]; } int main() { scanf("%d%lf%lf",&n,&k,&c); for(RG int i=1; i<=n; i++) scanf("%lf",&a[i]),a[i]+=a[i-1]; printf("%0.3lf\n",dfs(1,n)); return 0; }
3.bzoj1260 [CQOI2007]涂色paint
题目大意:给定一个长度为n的串,代表涂色目标,问最小涂色次数。涂色规则是,每次你可以将一个区间染成同一种颜色。
题解:用f[i][j]表示区间[i,j]变成目标区间的最少染色次数。下面考虑转移,因为可以发现,在我们递归处理任意区间[i,j]时,若要改动,将区间整段涂掉和小段小段涂是没差别的,所以最暴力的方法就是,我们每次选一种没改好的颜色直接整段涂掉就好,对每段没有涂好的再自己递归,最后求出最小值。但是我们还有更优的做法,更优的贪心策略显然是每次挑两个区间端点,端点颜色相同再整段涂掉,不同就再枚举断点递归求解就好了。考虑动态规划转移,初始态比较容易看出来,是f[i][i]=1,区间[i,j]转移的套路当然是[i+1,j]||[i,j-1][i+1,j-1]||f[i,k]+f[k+1,j]咯。
#include <stdio.h> #include <string.h> #define RG register #define dmin(a,b) ((a) < (b) ? (a) : (b)) char ch[51]; int f[51][51],n; int main() { memset(f,60,sizeof(f)); scanf("%s",ch+1); n=strlen(ch+1); for(RG int i=1; i<=n; i++) f[i][i]=1; for(RG int l=1; l<n; l++) for(RG int i=1; i+l<=n; i++) { if(ch[i] == ch[i+l]) { if(l == 1) f[i][i+1]=1; else f[i][i+l]=dmin(dmin(f[i+1][i+l],f[i][i+l-1]),f[i+1][i+l-1]+1); }else for(RG int j=i; j<i+l; j++) f[i][i+l]=dmin(f[i][i+l],f[i][j]+f[j+1][i+l]); } printf("%d\n",f[1][n]); return 0; }
4.bzoj1048 [HAOI2007]分割矩阵
题目大意:分割矩阵(a,b)为n个矩形,求n个矩形内数值和的均方差。分割规则是每次用一条直线分割矩形为两个小块。
题解:设f[a][b][c][d][e]为矩形(a,b,c,d)分割e次的最小均方差,转移一下就行了
#include <math.h> #include <stdio.h> #include <string.h> #define inf 1e9 #define RG register #define sqr(a) ((a)*(a)) #define dmin(a,b) ((a) < (b) ? (a) : (b)) bool vis[11][11][11][11][11]; int n,m,k,a[11][11],sum[11][11]; double f[11][11][11][11][11],ave; double dfs(RG int a,RG int b,RG int c,RG int d,RG int e) { double &ans=f[a][b][c][d][e]; if(vis[a][b][c][d][e]) return ans; vis[a][b][c][d][e]=1; if(!e) { ans=sum[b][d]-sum[a-1][d]-sum[b][c-1]+sum[a-1][c-1]; return ans=sqr(ans-ave); } ans=inf; for(RG int i=a; i<b; i++) for(RG int j=0; j<e; j++) ans=dmin(ans,dfs(a,i,c,d,j)+dfs(i+1,b,c,d,e-j-1)); for(RG int i=c; i<d; i++) for(RG int j=0; j<e; j++) ans=dmin(ans,dfs(a,b,c,i,j)+dfs(a,b,i+1,d,e-j-1)); return ans; } int main() { scanf("%d%d%d",&n,&m,&k); for(RG int i=1; i<=n; i++) for(RG int j=1; j<=m; j++) scanf("%d",&a[i][j]); for(RG int i=1; i<=n; i++) for(RG int j=1; j<=m; j++) sum[i][j]=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+a[i][j]; ave=(double)sum[n][m]/k; printf("%0.2lf\n",sqrt(dfs(1,n,1,m,k-1)/k)); return 0; }