[原创]插头DP小结(ACM by kuangbin)
这几天学习了一下插头DP,刷了11道题。对插头DP有了点理解。插头DP就先告一段落吧! 后面还有插头DP的广义路径和其它复杂应用,以后有机会再补上吧!
kuangbin
首先入门推荐的还是cdq的论文:《基于连通性状态压缩的动态规划问题》
http://wenku.baidu.com/view/4fe4ac659b6648d7c1c74633.html
上面的论文关于插头和轮廓线等概念讲得很清楚。而且关于插头DP的精髓也讲得很透了。
插头DP其实就是每格进行状态转移。
看懂原理和写代码还是有段距离的,可以结合代码去加深理解原理。同时形成适合自己风格的代码模板。
HDU 1693 Eat the Trees 此题是比较基础的入门题。
多条回路,求回路数问题。格子有障碍格子和非障碍格子两种。
解题报告:
http://www.cnblogs.com/kuangbin/archive/2012/10/01/2709834.html
#include<stdio.h> #include<string.h> #include<iostream> #include<algorithm> using namespace std; const int HASH=10007; const int STATE=1000010;//状态数 const int MAXD=15; int N,M; int code[MAXD],maze[MAXD][MAXD]; struct HASHMAP { int head[HASH],next[STATE],state[STATE],size; long long f[STATE]; void init() { size=0; memset(head,-1,sizeof(head)); } void push(int st,long long ans) { int i,h=st%HASH; for(i=head[h];i!=-1;i=next[i]) if(st==state[i]) { f[i]+=ans; return; } f[size]=ans; state[size]=st; next[size]=head[h]; head[h]=size++; } }hm[2]; void decode(int *code,int m,int st) { int i; for(i=m;i>=0;i--) { code[i]=st&1; st>>=1; } } int encode(int *code,int m) { int i,st=0; for(int i=0;i<=m;i++) { st<<=1; st|=code[i]; } return st; } void init() { int i,j; scanf("%d%d",&N,&M); for(i=1;i<=N;i++) for(int j=1;j<=M;j++) scanf("%d",&maze[i][j]); for(int i=1;i<=N;i++)maze[i][M+1]=0;//边界补0 for(int i=1;i<=M;i++)maze[N+1][i]=0; } void shift(int *code,int m)//换行的时候移位 { int i; for(i=m;i>0;i--) code[i]=code[i-1]; code[0]=0; } void dpblank(int i,int j,int cur) { int k,left,up; for(k=0;k<hm[cur].size;k++) { decode(code,M,hm[cur].state[k]); left=code[j-1]; up=code[j]; if(left&&up)//11 -> 00 { code[j-1]=code[j]=0; if(j==M)shift(code,M); hm[cur^1].push(encode(code,M),hm[cur].f[k]); } else if(left||up)//01 或 10 { if(maze[i][j+1]) { code[j-1]=0; code[j]=1; hm[cur^1].push(encode(code,M),hm[cur].f[k]); } if(maze[i+1][j]) { code[j-1]=1; code[j]=0; if(j==M)shift(code,M);//这个不要忘了! hm[cur^1].push(encode(code,M),hm[cur].f[k]); } } else { if(maze[i][j+1]&&maze[i+1][j]) { code[j]=code[j-1]=1; hm[cur^1].push(encode(code,M),hm[cur].f[k]); } } } } void dpblock(int i,int j,int cur) { int k; for(k=0;k<hm[cur].size;k++) { decode(code,M,hm[cur].state[k]); code[j-1]=code[j]=0; if(j==M)shift(code,M); hm[cur^1].push(encode(code,M),hm[cur].f[k]); } } void solve() { int i,j,cur=0; long long ans=0; hm[cur].init(); hm[cur].push(0,1); for(i=1;i<=N;i++) for(j=1;j<=M;j++) { hm[cur^1].init(); if(maze[i][j])dpblank(i,j,cur); else dpblock(i,j,cur); cur^=1; } for(i=0;i<hm[cur].size;i++) ans+=hm[cur].f[i]; printf("There are %I64d ways to eat the trees.\n",ans); } int main() { //freopen("in.txt","r",stdin); //freopen("out.txt","w",stdout); int T; int iCase=0; scanf("%d",&T); while(T--) { iCase++; printf("Case %d: ",iCase); init(); solve(); } return 0; }
Ural 1519 Formula 1 和论文上的同一道题。此题是求单回路数问题,和上面的一题在状态转移时有点不同。就是形成回路一定要在最后一个非障碍格子。
我的代码是用最小表示法做的。
解题报告:
http://www.cnblogs.com/kuangbin/archive/2012/09/29/2708989.html
/* 最小表示法 */ #include<stdio.h> #include<iostream> #include<string.h> #include<algorithm> using namespace std; const int MAXD=15; const int HASH=30007; const int STATE=1000010; int N,M; int maze[MAXD][MAXD]; int code[MAXD]; int ch[MAXD];//最小表示法使用 int ex,ey;//最后一个非障碍格子的坐标 struct HASHMAP { int head[HASH],next[STATE],size; long long state[STATE]; long long f[STATE]; void init() { size=0; memset(head,-1,sizeof(head)); } void push(long long st,long long ans) { int i; int h=st%HASH; for(i=head[h];i!=-1;i=next[i])//这里要注意是next if(state[i]==st) { f[i]+=ans; return; } state[size]=st; f[size]=ans; next[size]=head[h]; head[h]=size++; } }hm[2]; void decode(int *code,int m,long long st) { for(int i=m;i>=0;i--) { code[i]=st&7; st>>=3; } } long long encode(int *code,int m)//最小表示法 { int cnt=1; memset(ch,-1,sizeof(ch)); ch[0]=0; long long st=0; for(int i=0;i<=m;i++) { if(ch[code[i]]==-1)ch[code[i]]=cnt++; code[i]=ch[code[i]]; st<<=3; st|=code[i]; } return st; } void shift(int *code,int m) { for(int i=m;i>0;i--)code[i]=code[i-1]; code[0]=0; } void dpblank(int i,int j,int cur) { int k,left,up; for(k=0;k<hm[cur].size;k++) { decode(code,M,hm[cur].state[k]); left=code[j-1]; up=code[j]; if(left&&up) { if(left==up)//只能出现在最后一个非障碍格子 { if(i==ex&&j==ey) { code[j-1]=code[j]=0; if(j==M)shift(code,M); hm[cur^1].push(encode(code,M),hm[cur].f[k]); } } else//不在同一个连通分量则合并 { code[j-1]=code[j]=0; for(int t=0;t<=M;t++) if(code[t]==up) code[t]=left; if(j==M)shift(code,M); hm[cur^1].push(encode(code,M),hm[cur].f[k]); } } else if((left&&(!up))||((!left)&&up)) { int t; if(left)t=left; else t=up; if(maze[i][j+1]) { code[j-1]=0; code[j]=t; hm[cur^1].push(encode(code,M),hm[cur].f[k]); } if(maze[i+1][j]) { code[j-1]=t; code[j]=0; if(j==M)shift(code,M); hm[cur^1].push(encode(code,M),hm[cur].f[k]); } } else//无插头,则构造新的连通块 { if(maze[i][j+1]&&maze[i+1][j]) { code[j-1]=code[j]=13; hm[cur^1].push(encode(code,M),hm[cur].f[k]); } } } } void dpblock(int i,int j,int cur) { int k; for(k=0;k<hm[cur].size;k++) { decode(code,M,hm[cur].state[k]); code[j-1]=code[j]=0; if(j==M)shift(code,M); hm[cur^1].push(encode(code,M),hm[cur].f[k]); } } char str[MAXD]; void init() { memset(maze,0,sizeof(maze)); ex=0; for(int i=1;i<=N;i++) { scanf("%s",&str); for(int j=0;j<M;j++) { if(str[j]=='.') { ex=i; ey=j+1; maze[i][j+1]=1; } } } } void solve() { int i,j,cur=0; long long ans=0; hm[cur].init(); hm[cur].push(0,1); for(i=1;i<=N;i++) for(j=1;j<=M;j++) { hm[cur^1].init(); if(maze[i][j])dpblank(i,j,cur); else dpblock(i,j,cur); cur^=1; } for(i=0;i<hm[cur].size;i++) ans+=hm[cur].f[i]; printf("%I64d\n",ans); } int main() { //freopen("in.txt","r",stdin); //freopen("out.txt","w",stdout); while(scanf("%d%d",&N,&M)!=EOF) { init(); if(ex==0)//没有空的格子 { printf("0\n"); continue; } solve(); } return 0; }
FZU 1977 Pandora adventure 此题也是单回路数问题。但是格子有了三种:障碍格子,必走格子和选择性经过格子。这样导致最后一个非障碍格子不确定。所以增加一个标志位来记录是否形成回路。
解题报告:
http://www.cnblogs.com/kuangbin/archive/2012/10/01/2709967.html
/* FZU 1977 简单回路数问题,N*M(1<=N,M<=12)的方格中有障碍点,必走点和非必走点 因为回路只有一条,而形成回路的最后一个点又是不确定的。 所以额外增加一个标志位来记录是否形成回路。 如果已经形成回路,而后面又遇到插头或者必须走的点,则该方案不合法 G++ 375ms */ #include<stdio.h> #include<iostream> #include<algorithm> #include<string.h> using namespace std; const int MAXD=15; const int HASH=10007; const int STATE=100010; int N,M; int code[MAXD]; int maze[MAXD][MAXD];//0表示障碍点,1表示非必走点,2表示必走点 int ch[MAXD]; int isend;//是否形成回路标记位 struct HASHMAP { int head[HASH],next[STATE],size; long long state[STATE],f[STATE]; void init() { size=0; memset(head,-1,sizeof(head)); } void push(long long st,long long ans) { int i,h=st%HASH; for(i=head[h];i!=-1;i=next[i]) if(state[i]==st) { f[i]+=ans; return; } state[size]=st; f[size]=ans; next[size]=head[h]; head[h]=size++; } }hm[2]; void decode(int *code,int m,long long st)//注意要增加一个isend标记位 { for(int i=m;i>=0;i--) { code[i]=st&7; st>>=3; } isend=st&1;//isend标记位在st的最高一位 } long long encode(int *code,int m)//增加isend标记位 { long long st=isend;//最高位 int cnt=1; memset(ch,-1,sizeof(ch)); ch[0]=0; for(int i=0;i<=m;i++) { if(ch[code[i]]==-1)ch[code[i]]=cnt++; code[i]=ch[code[i]]; st<<=3; st|=code[i]; } return st; } void shift(int *code,int m) { for(int i=m;i>0;i--)code[i]=code[i-1]; code[0]=0; } void dpblank(int i,int j,int cur) { int k,left,up; for(k=0;k<hm[cur].size;k++) { decode(code,M,hm[cur].state[k]); left=code[j-1]; up=code[j]; if(isend) {//如果已经形成环路,后面又有插头或者有必过点,则是非法的 if(left||up||maze[i][j]==2)continue; code[j-1]=code[j]=0; if(j==M)shift(code,M); hm[cur^1].push(encode(code,M),hm[cur].f[k]); continue; } if(left&&up) { if(left==up) { code[j-1]=code[j]=0; isend=1; if(j==M)shift(code,M); hm[cur^1].push(encode(code,M),hm[cur].f[k]); } else //不在一个连通分量则合并 { code[j-1]=code[j]=0; for(int t=0;t<=M;t++) if(code[t]==up) code[t]=left; if(j==M)shift(code,M); hm[cur^1].push(encode(code,M),hm[cur].f[k]); } } else if((left&&(!up))||((!left)&&up)) { int t; if(left)t=left; else t=up; if(maze[i][j+1]) { code[j-1]=0; code[j]=t; hm[cur^1].push(encode(code,M),hm[cur].f[k]); } if(maze[i+1][j]) { code[j-1]=t; code[j]=0; if(j==M)shift(code,M); hm[cur^1].push(encode(code,M),hm[cur].f[k]); } } else { if(maze[i][j+1]&&maze[i+1][j]) { code[j-1]=code[j]=13; hm[cur^1].push(encode(code,M),hm[cur].f[k]); } if(maze[i][j]==1)//可以经过可以不经过的点 { code[j-1]=code[j]=0; if(j==M)shift(code,M); hm[cur^1].push(encode(code,M),hm[cur].f[k]); } } } } void dpblock(int i,int j,int cur) { int k; for(k=0;k<hm[cur].size;k++) { decode(code,M,hm[cur].state[k]); code[j-1]=code[j]=0; if(j==M)shift(code,M); hm[cur^1].push(encode(code,M),hm[cur].f[k]); } } char str[20]; void init() { scanf("%d%d",&N,&M); memset(maze,0,sizeof(maze)); for(int i=1;i<=N;i++) { scanf("%s",&str); for(int j=1;j<=M;j++) { if(str[j-1]=='*')maze[i][j]=1; else if(str[j-1]=='O')maze[i][j]=2; } } } void solve() { int i,j,cur=0; hm[cur].init(); hm[cur].push(0,1); for(int i=1;i<=N;i++) for(int j=1;j<=M;j++) { hm[cur^1].init(); if(maze[i][j])dpblank(i,j,cur); else dpblock(i,j,cur); cur^=1; } long long ans=0; for(i=0;i<hm[cur].size;i++) ans+=hm[cur].f[i]; printf("%I64d\n",ans); } int main() { // freopen("in.txt","r",stdin); // freopen("out.txt","w",stdout); int T; int iCase=0; scanf("%d",&T); while(T--) { iCase++; printf("Case %d: ",iCase); init(); solve(); } return 0; }
HDU 1964 Pipes 每个格子之间的墙壁有个花费,求用一个环经过每个格子仅一次的最小花费。此题不求方案数,只需要取最小值即可。而且所有格子都是非障碍格子,这样和Ural 1519就和类似了,而且更加简单。
解题报告:
http://www.cnblogs.com/kuangbin/archive/2012/09/30/2709372.html
/* HDU 1964 Pipes 插头DP 每个格子之间的墙壁有一个花费,求用一个环经过每个格子一次的最小花费 G++ 46ms */ #include<stdio.h> #include<iostream> #include<string.h> #include<algorithm> using namespace std; const int MAXD=15; const int HASH=10007; const int STATE=1000010; struct Node { int down,right;//每个格子下边和右边墙的花费 }node[MAXD][MAXD]; int N,M; int maze[MAXD][MAXD]; int code[MAXD]; int ch[MAXD];//最小表示法使用 int ex,ey;//最后一个非障碍格子的坐标 struct HASHMAP { int head[HASH],next[STATE],size; int dp[STATE]; long long state[STATE];//最小表示法,最大是8^11,就是2^33,所以用long long void init() { size=0; memset(head,-1,sizeof(head)); } void push(long long st,int ans) { int i,h=st%HASH; for(i=head[h];i!=-1;i=next[i]) if(state[i]==st) { if(dp[i]>ans)dp[i]=ans; return; } dp[size]=ans; state[size]=st; next[size]=head[h]; head[h]=size++; } }hm[2]; void decode(int *code,int m,long long st) { for(int i=m;i>=0;i--) { code[i]=st&7; st>>=3; } } long long encode(int *code,int m) { int cnt=1; memset(ch,-1,sizeof(ch)); ch[0]=0; long long st=0; for(int i=0;i<=m;i++) { if(ch[code[i]]==-1)ch[code[i]]=cnt++; code[i]=ch[code[i]]; st<<=3; st|=code[i]; } return st; } void shift(int *code,int m) { for(int i=m;i>0;i--)code[i]=code[i-1]; code[0]=0; } void dpblank(int i,int j,int cur) { int k,left,up; for(k=0;k<hm[cur].size;k++) { decode(code,M,hm[cur].state[k]); left=code[j-1]; up=code[j]; if(left&&up) { if(left==up)//只出现在最后一个格子 { if(i==ex&&j==ey) { code[j-1]=code[j]=0; if(j==M)shift(code,M); hm[cur^1].push(encode(code,M),hm[cur].dp[k]); } } else { code[j-1]=code[j]=0; for(int t=0;t<=M;t++) if(code[t]==left) code[t]=up; if(j==M)shift(code,M); hm[cur^1].push(encode(code,M),hm[cur].dp[k]); } } else if((left&&(!up))||((!left)&&up)) { int t; if(left)t=left; else t=up; if(maze[i][j+1]) { code[j-1]=0; code[j]=t; hm[cur^1].push(encode(code,M),hm[cur].dp[k]+node[i][j].right); } if(maze[i+1][j]) { code[j-1]=t; code[j]=0; if(j==M)shift(code,M); hm[cur^1].push(encode(code,M),hm[cur].dp[k]+node[i][j].down); } } else//无插头 { if(maze[i][j+1]&&maze[i+1][j]) { code[j-1]=code[j]=13; hm[cur^1].push(encode(code,M),hm[cur].dp[k]+node[i][j].down+node[i][j].right); } } } } char str[30]; void init() { scanf("%d%d%*c",&N,&M);//跳过一个字符 memset(maze,0,sizeof(maze)); for(int i=1;i<=N;i++) for(int j=1;j<=M;j++) maze[i][j]=1; gets(str); for(int i=1;i<N;i++) { gets(str); for(int j=1;j<M;j++) node[i][j].right=str[2*j]-'0'; gets(str); for(int j=1;j<=M;j++) node[i][j].down=str[2*j-1]-'0'; } gets(str); for(int j=1;j<M;j++) node[N][j].right=str[2*j]-'0'; gets(str); ex=N; ey=M; } void solve() { int i,j,cur=0; int ans=0; hm[cur].init(); hm[cur].push(0,0); for(i=1;i<=N;i++) for(j=1;j<=M;j++) { hm[cur^1].init(); dpblank(i,j,cur); cur^=1; } for(i=0;i<hm[cur].size;i++) ans+=hm[cur].dp[i]; printf("%d\n",ans); } int main() { // freopen("in.txt","r",stdin); // freopen("out.txt","w",stdout); int T; scanf("%d",&T); while(T--) { init(); solve(); } return 0; }
HDU 3377 Plan 从左上角走到右下角,每个格子有个分数。每个格子最多经过一次,可以不经过,求最大分数。 模板修改下就出来了。
解题报告:
http://www.cnblogs.com/kuangbin/archive/2012/10/01/2709773.html
/* HDU 3377 从左上角走到右下角。每个格子有个分数。 每个格子只能经过一次,可以不经过 求最大分数 G++ 140ms */ #include<stdio.h> #include<iostream> #include<algorithm> #include<string.h> using namespace std; const int MAXD=15; const int HASH=10007; const int STATE=1000010; int N,M; int maze[MAXD][MAXD]; int score[MAXD][MAXD]; int code[MAXD]; int ch[MAXD]; struct HASHMAP { int head[HASH],state[STATE],next[STATE],size; int dp[STATE]; void init() { size=0; memset(head,-1,sizeof(head)); } void push(int st,int ans) { int i,h=st%HASH; for(i=head[h];i!=-1;i=next[i]) if(state[i]==st) { if(dp[i]<ans)dp[i]=ans; return; } state[size]=st; dp[size]=ans; next[size]=head[h]; head[h]=size++; } }hm[2]; void decode(int *code,int m,int st) { for(int i=m;i>=0;i--) { code[i]=st&7; st>>=3; } } int encode(int *code,int m) { int cnt=1; memset(ch,-1,sizeof(ch)); ch[0]=0; int st=0; for(int i=0;i<=m;i++) { if(ch[code[i]]==-1)ch[code[i]]=cnt++; code[i]=ch[code[i]]; st<<=3; st|=code[i]; } return st; } void shift(int *code,int m) { for(int i=m;i>0;i--)code[i]=code[i-1]; code[0]=0; } void dpblank(int i,int j,int cur) { int k,left,up; for(k=0;k<hm[cur].size;k++) { decode(code,M,hm[cur].state[k]); left=code[j-1]; up=code[j]; if((i==1&&j==1)||(i==N&&j==M)) { if((left&&(!up))||((!left)&&up)) { code[j-1]=code[j]=0; if(j==M)shift(code,M); hm[cur^1].push(encode(code,M),hm[cur].dp[k]+score[i][j]); } else if(left==0&&up==0) { if(maze[i][j+1]) { code[j-1]=0; code[j]=13; hm[cur^1].push(encode(code,M),hm[cur].dp[k]+score[i][j]); } if(maze[i][j+1]) { code[j-1]=13; code[j]=0; if(j==M)shift(code,M); hm[cur^1].push(encode(code,M),hm[cur].dp[k]+score[i][j]); } } continue; } if(left&&up) { if(left==up)//没有这种情况,因为不形成环 { } else { code[j-1]=code[j]=0; for(int t=0;t<=M;t++)//这里少了个等号,查了好久的错 if(code[t]==up) code[t]=left; if(j==M)shift(code,M); hm[cur^1].push(encode(code,M),hm[cur].dp[k]+score[i][j]); } } else if((left&&(!up))||((!left)&&up)) { int t; if(left)t=left; else t=up; if(maze[i][j+1]) { code[j-1]=0; code[j]=t; hm[cur^1].push(encode(code,M),hm[cur].dp[k]+score[i][j]); } if(maze[i+1][j]) { code[j]=0; code[j-1]=t; if(j==M)shift(code,M); hm[cur^1].push(encode(code,M),hm[cur].dp[k]+score[i][j]); } } else { if(maze[i][j+1]&&maze[i+1][j]) { code[j]=code[j-1]=13; hm[cur^1].push(encode(code,M),hm[cur].dp[k]+score[i][j]); } code[j-1]=code[j]=0; if(j==M)shift(code,M); hm[cur^1].push(encode(code,M),hm[cur].dp[k]); } } } void init() { memset(maze,0,sizeof(maze)); for(int i=1;i<=N;i++) for(int j=1;j<=M;j++) { maze[i][j]=1; scanf("%d",&score[i][j]); } } void solve() { int i,j,cur=0; hm[cur].init(); hm[cur].push(0,0); for(int i=1;i<=N;i++) for(int j=1;j<=M;j++) { hm[cur^1].init(); dpblank(i,j,cur); cur^=1; } int ans=0; for(int i=0;i<hm[cur].size;i++) ans+=hm[cur].dp[i]; printf("%d\n",ans); } int main() { // freopen("in.txt","r",stdin); // freopen("out.txt","w",stdout); int iCase=0; while(scanf("%d%d",&N,&M)!=EOF) { iCase++; printf("Case %d: ",iCase); init(); if(N==1&&M==1) { printf("%d\n",score[1][1]); continue; } solve(); } return 0; } /* Sample Input 2 2 1 2 3 1 3 3 0 -20 100 1 -20 -20 1 1 1 Sample Output Case 1: 5 Case 2: 61 */
POJ 1739 Tony's Tour 楼教主的男人八题之一。从左下角走到右下角,每个非障碍格子仅走一次的方法数。一种方法是在后面增加两行转换成回路问题,这样就和ural 1519一样了。也可以不增加行,直接特殊处理下即可。
解题报告:
http://www.cnblogs.com/kuangbin/archive/2012/09/30/2709114.html
/* POJ 1739 题目意思就是从左下角走到右下角,每个非障碍格子都走一遍的方法数 转换成回路问题。 在最后加两行 .########. .......... 这样就转成回路问题了,就和URAL 1519 一样的做法了 G++ 47ms */ #include<iostream> #include<string.h> #include<algorithm> #include<stdio.h> using namespace std; const int MAXD=15; const int HASH=10007; const int STATE=1000010; int N,M; int maze[MAXD][MAXD]; int code[MAXD]; int ch[MAXD];//最小表示法使用 int ex,ey;//最后一个非障碍格子的坐标 struct HASHMAP { int head[HASH],next[STATE],size; long long state[STATE],f[STATE]; void init() { size=0; memset(head,-1,sizeof(head)); } void push(long long st,long long ans) { int i; int h=st%HASH; for(i=head[h];i!=-1;i=next[i]) if(state[i]==st) { f[i]+=ans; return; } state[size]=st; f[size]=ans; next[size]=head[h]; head[h]=size++; } }hm[2]; void decode(int *code,int m,long long st) { for(int i=m;i>=0;i--) { code[i]=st&7; st>>=3; } } long long encode(int *code,int m) { int cnt=1; memset(ch,-1,sizeof(ch)); ch[0]=0; long long st=0; for(int i=0;i<=m;i++) { if(ch[code[i]]==-1)ch[code[i]]=cnt++; code[i]=ch[code[i]]; st<<=3; st|=code[i]; } return st; } void shift(int *code,int m) { for(int i=m;i>0;i--)code[i]=code[i-1]; code[0]=0; } void dpblank(int i,int j,int cur) { int k,left,up; for(k=0;k<hm[cur].size;k++) { decode(code,M,hm[cur].state[k]); left=code[j-1]; up=code[j]; if(left&&up) { if(left==up) { if(i==ex&&j==ey)//只能出现在最后一个非障碍格子 { code[j]=code[j-1]=0; if(j==M)shift(code,M); hm[cur^1].push(encode(code,M),hm[cur].f[k]); } } else //不在同一个连通分量则合并 { code[j-1]=code[j]=0; for(int t=0;t<=M;t++) if(code[t]==left) code[t]=up; if(j==M)shift(code,M); hm[cur^1].push(encode(code,M),hm[cur].f[k]); } } else if((left&&(!up))||((!left)&&up)) { int t; if(left)t=left; else t=up; if(maze[i][j+1]) { code[j-1]=0; code[j]=t; hm[cur^1].push(encode(code,M),hm[cur].f[k]); } if(maze[i+1][j]) { code[j-1]=t; code[j]=0; if(j==M)shift(code,M); hm[cur^1].push(encode(code,M),hm[cur].f[k]); } } else { if(maze[i][j+1]&&maze[i+1][j]) { code[j-1]=code[j]=13; hm[cur^1].push(encode(code,M),hm[cur].f[k]); } } } } void dpblock(int i,int j,int cur) { int k; for(k=0;k<hm[cur].size;k++) { decode(code,M,hm[cur].state[k]); code[j-1]=code[j]=0; if(j==M)shift(code,M); hm[cur^1].push(encode(code,M),hm[cur].f[k]); } } char str[20]; void init() { memset(maze,0,sizeof(maze)); for(int i=1;i<=N;i++) { scanf("%s",&str); for(int j=0;j<M;j++) if(str[j]=='.') { maze[i][j+1]=1; } } maze[N+1][1]=maze[N+1][M]=1; for(int i=2;i<M;i++)maze[N+1][i]=0; for(int i=1;i<=M;i++)maze[N+2][i]=1; N+=2; ex=N,ey=M; } void solve() { int i,j,cur=0; hm[cur].init(); hm[cur].push(0,1); for(i=1;i<=N;i++) for(j=1;j<=M;j++) { hm[cur^1].init(); if(maze[i][j])dpblank(i,j,cur); else dpblock(i,j,cur); cur^=1; } long long ans=0; for(int i=0;i<hm[cur].size;i++) ans+=hm[cur].f[i]; printf("%I64d\n",ans); } int main() { //freopen("in.txt","r",stdin); //freopen("out.txt","w",stdout); while(scanf("%d%d",&N,&M)) { if(N==0&&M==0)break; init(); solve(); } return 0; }
/* POJ 1739 不增加行。 起点和终点特殊处理 G++ 47ms */ #include<iostream> #include<string.h> #include<algorithm> #include<stdio.h> using namespace std; const int MAXD=15; const int HASH=10007; const int STATE=1000010; int N,M; int maze[MAXD][MAXD]; int code[MAXD]; int ch[MAXD];//最小表示法使用 int ex,ey;//最后一个非障碍格子的坐标 struct HASHMAP { int head[HASH],next[STATE],size; long long state[STATE],f[STATE]; void init() { size=0; memset(head,-1,sizeof(head)); } void push(long long st,long long ans) { int i; int h=st%HASH; for(i=head[h];i!=-1;i=next[i]) if(state[i]==st) { f[i]+=ans; return; } state[size]=st; f[size]=ans; next[size]=head[h]; head[h]=size++; } }hm[2]; void decode(int *code,int m,long long st) { for(int i=m;i>=0;i--) { code[i]=st&7; st>>=3; } } long long encode(int *code,int m) { int cnt=1; memset(ch,-1,sizeof(ch)); ch[0]=0; long long st=0; for(int i=0;i<=m;i++) { if(ch[code[i]]==-1)ch[code[i]]=cnt++; code[i]=ch[code[i]]; st<<=3; st|=code[i]; } return st; } void shift(int *code,int m) { for(int i=m;i>0;i--)code[i]=code[i-1]; code[0]=0; } void dpblank(int i,int j,int cur) { int k,left,up; for(k=0;k<hm[cur].size;k++) { decode(code,M,hm[cur].state[k]); left=code[j-1]; up=code[j]; if((i==N&&j==1)||(i==N&&j==M))//起点和终点 { if((left&&(!up))||((!left)&&up)) { code[j]=code[j-1]=0; if(j==M)shift(code,M); hm[cur^1].push(encode(code,M),hm[cur].f[k]); } else if(left==0&&up==0) { if(maze[i][j+1]) { code[j-1]=0; code[j]=13; hm[cur^1].push(encode(code,M),hm[cur].f[k]); } if(maze[i+1][j]) { code[j-1]=13; code[j]=0; if(j==M)shift(code,M); hm[cur^1].push(encode(code,M),hm[cur].f[k]); } } continue; } if(left&&up) { if(left==up) {//这种情况不能发生 /* if(i==ex&&j==ey)//只能出现在最后一个非障碍格子 { code[j]=code[j-1]=0; if(j==M)shift(code,M); hm[cur^1].push(encode(code,M),hm[cur].f[k]); }*/ } else //不在同一个连通分量则合并 { code[j-1]=code[j]=0; for(int t=0;t<=M;t++) if(code[t]==left) code[t]=up; if(j==M)shift(code,M); hm[cur^1].push(encode(code,M),hm[cur].f[k]); } } else if((left&&(!up))||((!left)&&up)) { int t; if(left)t=left; else t=up; if(maze[i][j+1]) { code[j-1]=0; code[j]=t; hm[cur^1].push(encode(code,M),hm[cur].f[k]); } if(maze[i+1][j]) { code[j-1]=t; code[j]=0; if(j==M)shift(code,M); hm[cur^1].push(encode(code,M),hm[cur].f[k]); } } else { if(maze[i][j+1]&&maze[i+1][j]) { code[j-1]=code[j]=13; hm[cur^1].push(encode(code,M),hm[cur].f[k]); } } } } void dpblock(int i,int j,int cur) { int k; for(k=0;k<hm[cur].size;k++) { decode(code,M,hm[cur].state[k]); code[j-1]=code[j]=0; if(j==M)shift(code,M); hm[cur^1].push(encode(code,M),hm[cur].f[k]); } } char str[20]; void init() { memset(maze,0,sizeof(maze)); for(int i=1;i<=N;i++) { scanf("%s",&str); for(int j=0;j<M;j++) if(str[j]=='.') { maze[i][j+1]=1; } } } void solve() { int i,j,cur=0; hm[cur].init(); hm[cur].push(0,1); for(i=1;i<=N;i++) for(j=1;j<=M;j++) { hm[cur^1].init(); if(maze[i][j])dpblank(i,j,cur); else dpblock(i,j,cur); cur^=1; } long long ans=0; for(int i=0;i<hm[cur].size;i++) ans+=hm[cur].f[i]; printf("%I64d\n",ans); } int main() { //freopen("in.txt","r",stdin); // freopen("out.txt","w",stdout); while(scanf("%d%d",&N,&M)) { if(N==0&&M==0)break; init(); solve(); } return 0; }
POJ 3133 Manhattan Wiring 格子中有两个2,两个3.求把两个2连起来,两个3连起来。求经过总的格子数的总和减2. 两条路径不能交叉。有障碍格子。非障碍格子最多经过一次。插头出需要记录三种状态:没有插头、2号插头、3号插头。可以用三进制。但是考虑到4进制更加高效,我用的四进制做的。
解题报告:
http://www.cnblogs.com/kuangbin/archive/2012/09/30/2709263.html
/* POJ 3133 连接2的插头为2,连接3的插头为3 没有插头为0 用四进制表示(四进制比三进制高效) G++ 391ms */ #include<stdio.h> #include<iostream> #include<string.h> #include<algorithm> using namespace std; const int MAXD=15; const int HASH=10007; const int STATE=1000010; int N,M; int maze[MAXD][MAXD];//0表示障碍,1是非障碍,2和3 int code[MAXD]; //0表示没有插头,2表示和插头2相连,3表示和插头3相连 struct HASHMAP { int head[HASH],next[STATE],size; int state[STATE]; int dp[STATE]; void init() { size=0; memset(head,-1,sizeof(head)); } void push(int st,int ans) { int i,h=st%HASH; for(i=head[h];i!=-1;i=next[i]) if(state[i]==st) { if(dp[i]>ans)dp[i]=ans; return; } state[size]=st; dp[size]=ans; next[size]=head[h]; head[h]=size++; } }hm[2]; void decode(int *code,int m,int st)//四进制 { int i; for(int i=m;i>=0;i--) { code[i]=(st&3); st>>=2; } } int encode(int *code,int m) { int i; int st=0; for(int i=0;i<=m;i++) { st<<=2; st|=code[i]; } return st; } void shift(int *code,int m)//换行 { for(int i=m;i>0;i--)code[i]=code[i-1]; code[0]=0; } void dpblank(int i,int j,int cur) { int k,left,up; for(k=0;k<hm[cur].size;k++) { decode(code,M,hm[cur].state[k]); left=code[j-1]; up=code[j]; if(left&&up) { if(left==up)//只能是相同的插头 { code[j-1]=code[j]=0; if(j==M)shift(code,M); hm[cur^1].push(encode(code,M),hm[cur].dp[k]+1); } } else if((left&&(!up))||((!left)&&up))//只有一个插头 { int t; if(left)t=left; else t=up;//这里少写个else ,查了好久的错误 if(maze[i][j+1]==1||maze[i][j+1]==t)//插头从右边出来 { code[j-1]=0; code[j]=t; hm[cur^1].push(encode(code,M),hm[cur].dp[k]+1); } if(maze[i+1][j]==1||maze[i+1][j]==t)//插头从下边出来 { code[j]=0; code[j-1]=t; if(j==M)shift(code,M); hm[cur^1].push(encode(code,M),hm[cur].dp[k]+1); } } else if(left==0&&up==0)//没有插头 { code[j-1]=code[j]=0;//不加插头 if(j==M)shift(code,M); hm[cur^1].push(encode(code,M),hm[cur].dp[k]); if(maze[i][j+1]&&maze[i+1][j]) { if(maze[i][j+1]==1&&maze[i+1][j]==1) { decode(code,M,hm[cur].state[k]); code[j-1]=code[j]=2;//加2号插头 hm[cur^1].push(encode(code,M),hm[cur].dp[k]+1); //decode(code,M,hm[cur].state[k]); code[j-1]=code[j]=3;//加3号插头 hm[cur^1].push(encode(code,M),hm[cur].dp[k]+1); } else if((maze[i][j+1]==2&&maze[i+1][j]==1)||(maze[i+1][j]==2&&maze[i][j+1]==1)||(maze[i][j+1]==2&&maze[i+1][j]==2)) { decode(code,M,hm[cur].state[k]); code[j-1]=code[j]=2; hm[cur^1].push(encode(code,M),hm[cur].dp[k]+1); } else if((maze[i][j+1]==3&&maze[i+1][j]==1)||(maze[i+1][j]==3&&maze[i][j+1]==1)||(maze[i][j+1]==3&&maze[i+1][j]==3)) { decode(code,M,hm[cur].state[k]); code[j-1]=code[j]=3; hm[cur^1].push(encode(code,M),hm[cur].dp[k]+1); } } } } } void dpblock(int i,int j,int cur) { int k; for(k=0;k<hm[cur].size;k++) { decode(code,M,hm[cur].state[k]); if(code[j-1]!=0||code[j]!=0)continue; code[j-1]=code[j]=0;//不加插头 if(j==M)shift(code,M); hm[cur^1].push(encode(code,M),hm[cur].dp[k]); } } void dp_2(int i,int j,int cur) { int left,up,k; for(k=0;k<hm[cur].size;k++) { decode(code,M,hm[cur].state[k]); left=code[j-1]; up=code[j]; if((left==2&&up==0)||(left==0&&up==2)) { code[j-1]=code[j]=0; if(j==M)shift(code,M); hm[cur^1].push(encode(code,M),hm[cur].dp[k]+1); } else if(left==0&&up==0) { if(maze[i][j+1]==1||maze[i][j+1]==2) { code[j-1]=0; code[j]=2; hm[cur^1].push(encode(code,M),hm[cur].dp[k]+1); } if(maze[i+1][j]==1||maze[i+1][j]==2) { code[j-1]=2; code[j]=0; if(j==M)shift(code,M); hm[cur^1].push(encode(code,M),hm[cur].dp[k]+1); } } } } void dp_3(int i,int j,int cur) { int left,up,k; for(k=0;k<hm[cur].size;k++) { decode(code,M,hm[cur].state[k]); left=code[j-1]; up=code[j]; if((left==3&&up==0)||(left==0&&up==3)) { code[j-1]=code[j]=0; if(j==M)shift(code,M); hm[cur^1].push(encode(code,M),hm[cur].dp[k]+1); } else if(left==0&&up==0) { if(maze[i][j+1]==1||maze[i][j+1]==3) { code[j-1]=0; code[j]=3; hm[cur^1].push(encode(code,M),hm[cur].dp[k]+1); } if(maze[i+1][j]==1||maze[i+1][j]==3) { code[j-1]=3; code[j]=0; if(j==M)shift(code,M); hm[cur^1].push(encode(code,M),hm[cur].dp[k]+1); } } } } void init() { memset(maze,0,sizeof(maze)); for(int i=1;i<=N;i++) for(int j=1;j<=M;j++) { scanf("%d",&maze[i][j]); //if(maze[i][j]==0)maze[i][j]=1; //if(maze[i][j]==1)maze[i][j]=0; //上面的写法是错的,!!! if(maze[i][j]==1||maze[i][j]==0)maze[i][j]^=1;//0变1,1变0 } } void solve() { int i,j,cur=0; hm[cur].init(); hm[cur].push(0,0); for(int i=1;i<=N;i++) for(int j=1;j<=M;j++) { hm[cur^1].init(); if(maze[i][j]==0)dpblock(i,j,cur); else if(maze[i][j]==1)dpblank(i,j,cur); else if(maze[i][j]==2)dp_2(i,j,cur); else if(maze[i][j]==3)dp_3(i,j,cur); cur^=1; } int ans=0; for(int i=0;i<hm[cur].size;i++) ans+=hm[cur].dp[i]; if(ans>0)ans-=2; printf("%d\n",ans); } int main() { //freopen("in.txt","r",stdin); //freopen("out.txt","w",stdout); while(scanf("%d%d",&N,&M)) { if(N==0&&M==0)break; init(); solve(); } return 0; }
ZOJ 3466 The Hive II 和HDU 1693一样,求多回路问题。但是四边形换成了六边行。难度相应提高了。做的方法是一样的。我是倒过来,按照列来转移的,稍微简单一下。按照行可以做,但是讨论比较多,我没有去尝试。
解题报告:
http://www.cnblogs.com/kuangbin/archive/2012/09/29/2708909.html
/* ZOJ 3466 C++ 3850ms 62768K 原来ZOJ中的long long要用%lld输出啊 对ZOJ不熟悉啊,被坑了好久 */ #include<stdio.h> #include<string.h> #include<algorithm> #include<iostream> using namespace std; const int HASH=10007; const int STATE=2000010; const int MAXD=32; int N,M; int code[MAXD],maze[MAXD][MAXD]; struct HASHMAP { int head[HASH],next[STATE],state[STATE],size; long long f[STATE]; void init() { size=0; memset(head,-1,sizeof(head)); } void push(int st,long long ans) { int i,h=st%HASH; for(i=head[h];i!=-1;i=next[i]) if(st==state[i]) { f[i]+=ans; return; } f[size]=ans; state[size]=st; next[size]=head[h]; head[h]=size++; } }hm[2]; void decode(int *code,int m,int st) { int i; for(i=m;i>=0;i--) { code[i]=st&1; st>>=1; } } int encode(int *code,int m) { int i,st=0; for(i=0;i<=m;i++) { st<<=1; st|=code[i]; } return st; } void init() { N=8;//倒过来,8行 int t; scanf("%d",&t); memset(maze,0,sizeof(maze)); for(int i=1;i<=N;i++) for(int j=1;j<=M;j++) maze[i][j]=1; char str[10]; while(t--) { scanf("%s",&str); maze[str[1]-'A'+1][M-(str[0]-'A')]=0; } } void shift(int *code,int m)//偶数行换奇数行的时候要变化 { for(int i=m;i>1;i--) code[i]=code[i-2]; code[0]=0; code[1]=0; } void dpblank(int i,int j,int cur) { int k,left,up1,up2; int t1,t2; if(i%2==0)t1=j,t2=j+1; else t1=j-1,t2=j; for(k=0;k<hm[cur].size;k++) { decode(code,2*M,hm[cur].state[k]); left=code[2*(j-1)]; up1=code[2*j-1]; up2=code[2*j]; // printf("%d %d: %d %d %d\n",i,j,left,up1,up2); if((left==1&&up1==1&&up2==0)||(left==0&&up1==1&&up2==1)||(left==1&&up1==0&&up2==1)) { code[2*j-2]=code[2*j-1]=code[2*j]=0; if(j==M&&i%2==0)shift(code,2*M); hm[cur^1].push(encode(code,2*M),hm[cur].f[k]); } else if((left==1&&up1==0&&up2==0)||(left==0&&up1==1&&up2==0)||(left==0&&up1==0&&up2==1)) { if(maze[i+1][t1]) { code[2*j-2]=1; code[2*j-1]=code[2*j]=0; if(j==M&&i%2==0)shift(code,2*M); hm[cur^1].push(encode(code,2*M),hm[cur].f[k]); } if(maze[i+1][t2]) { code[2*j-2]=code[2*j]=0; code[2*j-1]=1; hm[cur^1].push(encode(code,2*M),hm[cur].f[k]); } if(maze[i][j+1]) { code[2*j-2]=code[2*j-1]=0; code[2*j]=1; hm[cur^1].push(encode(code,2*M),hm[cur].f[k]); } } else if(left==0&&up1==0&&up2==0) { if(maze[i+1][t1]&&maze[i+1][t2]) { code[2*j-2]=code[2*j-1]=1; code[2*j]=0; hm[cur^1].push(encode(code,2*M),hm[cur].f[k]); } if(maze[i+1][t1]&&maze[i][j+1]) { code[2*j-2]=code[2*j]=1; code[2*j-1]=0; hm[cur^1].push(encode(code,2*M),hm[cur].f[k]); } if(maze[i+1][t2]&&maze[i][j+1]) { code[2*j-2]=0; code[2*j-1]=code[2*j]=1; hm[cur^1].push(encode(code,2*M),hm[cur].f[k]); } } } } void dpblock(int i,int j,int cur) { int k; for(k=0;k<hm[cur].size;k++) { decode(code,2*M,hm[cur].state[k]); code[2*j-2]=code[2*j-1]=code[2*j]=0; if(j==M&&i%2==0)shift(code,2*M); hm[cur^1].push(encode(code,2*M),hm[cur].f[k]); } } void solve() { int i,j,cur=0; long long ans=0; hm[cur].init(); hm[cur].push(0,1); for(i=1;i<=N;i++) for(j=1;j<=M;j++) { hm[cur^1].init(); if(maze[i][j])dpblank(i,j,cur); else dpblock(i,j,cur); cur^=1; } for(i=0;i<hm[cur].size;i++) if(hm[cur].state[i]==0) ans+=hm[cur].f[i]; printf("%lld\n",ans);//ZOJ中的C++要lld才能AC的 } int main() { //freopen("in.txt","r",stdin); //freopen("out.txt","w",stdout); while(scanf("%d",&M)!=EOF) { init(); solve(); } return 0; }
ZOJ 3256 Tour in the Castle 这题数据很大。求左上角到右下角的路径数。经过所以格子一次。要用矩阵加速。由于每一列都是一样的,状态转移就相同。用插头DP求出列与列之间的状态转移,然后用矩阵乘法。这题比较难
解题报告:
http://www.cnblogs.com/kuangbin/archive/2012/10/01/2709831.html
/* ZOJ 3256 N*M(2<=N<=7,1<=M<=10^9)的方格,问从左上角的格子到左下角的格子, 而且仅经过所有格子一次的路径数 插头DP+矩阵加速 对于一个图的邻接矩阵的N次方,其中(i,j)位置上的元素表示 点i经过N步到达点j的方案数 */ #include<stdio.h> #include<iostream> #include<algorithm> #include<string.h> using namespace std; const int STATE=1010; const int HASH=419;//这个小一点,效率高 const int MOD=7777777; int N,M; int D; int code[10]; int ch[10]; int g[200][200];//状态转移图 struct Matrix { int n,m; int mat[200][200]; }; Matrix mul(Matrix a,Matrix b)//矩阵相乘,要保证a的列数和b的行数相等 { Matrix ret; ret.n=a.n; ret.m=b.m; long long sum; for(int i=0;i<a.n;i++) for(int j=0;j<b.m;j++) { sum=0; for(int k=0;k<a.m;k++) { sum+=(long long)a.mat[i][k]*b.mat[k][j]; //sum%=MOD;//加了这句话就会TLE,坑啊。。。 } ret.mat[i][j]=sum%MOD; } return ret; } Matrix pow_M(Matrix a,int n)//方阵的n次方 { Matrix ret=a; memset(ret.mat,0,sizeof(ret.mat)); for(int i=0;i<a.n;i++)ret.mat[i][i]=1;//单位阵 Matrix temp=a; while(n) { if(n&1)ret=mul(ret,temp); temp=mul(temp,temp); n>>=1; } return ret; } struct HASHMAP { int head[HASH],next[STATE],size; int state[STATE]; void init() { size=0; memset(head,-1,sizeof(head)); } int push(int st) { int i,h=st%HASH; for(i=head[h];i!=-1;i=next[i]) if(state[i]==st) return i; state[size]=st; next[size]=head[h]; head[h]=size++; return size-1; } }hm; void decode(int *code,int n,int st) { for(int i=n-1;i>=0;i--) { code[i]=st&3; st>>=2; } } int encode(int *code,int n) { int cnt=1; memset(ch,-1,sizeof(ch)); ch[0]=0; int st=0; for(int i=0;i<n;i++) { if(ch[code[i]]==-1)ch[code[i]]=cnt++; code[i]=ch[code[i]]; st<<=2; st|=code[i]; } return st; } bool check(int st,int nst)//判断两种状态能不能转移 { decode(code,N,st); int flag=0;//标记格子上边是否有插头 int cnt=0; int k; for(int i=0;i<N;i++) { if(flag==0)//这个格子上边没有插头 { if(code[i]==0&&(nst&(1<<i))==0)//左边和右边都没有插头 return false; if(code[i]&&(nst&(1<<i)))continue; if(code[i])flag=code[i];//插头从左边过来,从下边出去 else flag=-1;//插头从下边进来从右边出去 k=i; } else { if(code[i]&&(nst&(1<<i)))//左边和右边和上边都有插头 return false; if(code[i]==0&&(nst&(1<<i))==0)continue; if(code[i]) { if(code[i]==flag&&((nst!=0)||i!=N-1))return false;//只有最后一个格子才能合起来 if(flag>0) { for(int j=0;j<N;j++) if(code[j]==code[i]&&j!=i) code[j]=code[k]; code[i]=code[k]=0; } else { code[k]=code[i]; code[i]=0; } } else { if(flag>0)code[i]=code[k],code[k]=0; else code[i]=code[k]=N+(cnt++); } flag=0; } } if(flag!=0)return false; return true; } struct Node { int g[200][200]; int D; }node[20];//打表之用 void init() { if(node[N].D!=0) { memcpy(g,node[N].g,sizeof(node[N].g)); D=node[N].D; return; } int st,nst; hm.init(); memset(code,0,sizeof(code)); code[0]=code[N-1]=1; hm.push(0); hm.push(encode(code,N)); memset(g,0,sizeof(g)); for(int i=1;i<hm.size;i++) { st=hm.state[i]; for(nst=0;nst<(1<<N);nst++) if(check(st,nst)) { int j=hm.push(encode(code,N)); g[i][j]=1; } } D=hm.size; memcpy(node[N].g,g,sizeof(g)); node[N].D=D; } void solve() { Matrix temp; temp.n=temp.m=D; memcpy(temp.mat,g,sizeof(g)); Matrix ans=pow_M(temp,M); if(ans.mat[1][0]==0)printf("Impossible\n"); else printf("%d\n",ans.mat[1][0]%MOD); } int main() { //freopen("in.txt","r",stdin); //freopen("out.txt","w",stdout); for(int i=0;i<20;i++)node[i].D=0; while(scanf("%d%d",&N,&M)==2) { init(); solve(); } return 0; }
ZOJ 3213 Beautiful Meadow 此题求简单路径。得到最大的分数。
用最小表示法,需要增加标志位记录独立插头个数。要使独立插头个数小于等于2.
解题报告:
http://www.cnblogs.com/kuangbin/archive/2012/10/02/2710055.html
/* ZOJ 3213 */ #include<stdio.h> #include<string.h> #include<algorithm> #include<iostream> using namespace std; const int MAXD=15; const int HASH=10007; const int STATE=1000010; int N,M; int maze[MAXD][MAXD]; int code[MAXD]; int ch[MAXD]; int num;//独立插头的个数 int ans;//答案 struct HASHMAP { int head[HASH],next[STATE],size; int state[STATE],dp[STATE]; void init() { size=0; memset(head,-1,sizeof(head)); } void push(int st,int ans) { int i,h=st%HASH; for(i=head[h];i!=-1;i=next[i]) if(state[i]==st) { if(dp[i]<ans)dp[i]=ans; return; } state[size]=st; dp[size]=ans; next[size]=head[h]; head[h]=size++; } }hm[2]; void decode(int *code,int m,int st) { num=st&7;//独立插头个数 st>>=3; for(int i=m;i>=0;i--) { code[i]=st&7; st>>=3; } } int encode(int *code,int m) { int cnt=1; memset(ch,-1,sizeof(ch)); ch[0]=0; int st=0; for(int i=0;i<=m;i++) { if(ch[code[i]]==-1)ch[code[i]]=cnt++; code[i]=ch[code[i]]; st<<=3; st|=code[i]; } st<<=3; st|=num; return st; } void shift(int *code,int m) { for(int i=m;i>0;i--)code[i]=code[i-1]; code[0]=0; } void dpblank(int i,int j,int cur) { int k,left,up; for(k=0;k<hm[cur].size;k++) { decode(code,M,hm[cur].state[k]); left=code[j-1]; up=code[j]; if(left&&up) { if(left!=up) { code[j-1]=code[j]=0; for(int t=0;t<=M;t++) if(code[t]==up) code[t]=left; if(j==M)shift(code,M); hm[cur^1].push(encode(code,M),hm[cur].dp[k]+maze[i][j]); // hm[cur^1].push(encode(code,j==M?M-1:M),hm[cur].dp[k]+maze[i][j]); } } else if(left||up) { int t; if(left)t=left; else t=up; if(maze[i][j+1]) { code[j-1]=0; code[j]=t; hm[cur^1].push(encode(code,M),hm[cur].dp[k]+maze[i][j]); } if(maze[i+1][j]) { code[j-1]=t; code[j]=0; hm[cur^1].push(encode(code,j==M?M-1:M),hm[cur].dp[k]+maze[i][j]); } if(num<2) { num++; code[j-1]=code[j]=0; hm[cur^1].push(encode(code,j==M?M-1:M),hm[cur].dp[k]+maze[i][j]); } } else { code[j-1]=code[j]=0; hm[cur^1].push(encode(code,j==M?M-1:M),hm[cur].dp[k]); if(maze[i][j+1]&&maze[i+1][j]) { code[j-1]=code[j]=13; hm[cur^1].push(encode(code,M),hm[cur].dp[k]+maze[i][j]); } if(num<2) { num++; if(maze[i][j+1]) { code[j]=13; code[j-1]=0; hm[cur^1].push(encode(code,M),hm[cur].dp[k]+maze[i][j]); } if(maze[i+1][j]) { code[j-1]=13; code[j]=0; hm[cur^1].push(encode(code,j==M?M-1:M),hm[cur].dp[k]+maze[i][j]); } } } } } void dpblock(int i,int j,int cur) { int k; for(k=0;k<hm[cur].size;k++) { decode(code,M,hm[cur].state[k]);//这个忘记了!!! code[j-1]=code[j]=0; if(j==M)shift(code,M); hm[cur^1].push(encode(code,M),hm[cur].dp[k]); } } void init() { scanf("%d%d",&N,&M); ans=0; memset(maze,0,sizeof(maze));//初始化别忘记了 for(int i=1;i<=N;i++) for(int j=1;j<=M;j++) { scanf("%d",&maze[i][j]); if(maze[i][j]>ans)ans=maze[i][j]; } } void solve() { int i,j,cur=0; hm[cur].init(); hm[cur].push(0,0); for(i=1;i<=N;i++) for(int j=1;j<=M;j++) { hm[cur^1].init(); if(maze[i][j])dpblank(i,j,cur); else dpblock(i,j,cur); cur^=1; } for(i=0;i<hm[cur].size;i++) if(hm[cur].dp[i]>ans) ans=hm[cur].dp[i]; printf("%d\n",ans); } int main() { // freopen("in.txt","r",stdin); // freopen("out.txt","w",stdout); int T; scanf("%d",&T); while(T--) { init(); solve(); } return 0; } /* Sample Input 2 1 1 10 1 2 5 0 Sample Output 10 5 */
HDU 4285 circuits 这是天津网络赛的题目。求K个回路的方案数。而且不能是环套环。
增加个标志位来记录形成的回路个数。而且注意避免环套环的情况。
解题报告:
http://www.cnblogs.com/kuangbin/archive/2012/10/02/2710308.html
/* HDU 4285 要形成刚好K条回路的方法数 要避免环套环的情况。 所以形成回路时,要保证两边的插头数是偶数 G++ 11265ms 11820K C++ 10656ms 11764K */ #include<stdio.h> #include<iostream> #include<algorithm> #include<string.h> using namespace std; const int MAXD=15; const int STATE=1000010; const int HASH=300007;//这个大一点可以防止TLE,但是容易MLE const int MOD=1000000007; int N,M,K; int maze[MAXD][MAXD]; int code[MAXD]; int ch[MAXD]; int num;//圈的个数 struct HASHMAP { int head[HASH],next[STATE],size; long long state[STATE]; int f[STATE]; void init() { size=0; memset(head,-1,sizeof(head)); } void push(long long st,int ans) { int i; int h=st%HASH; for(i=head[h];i!=-1;i=next[i]) if(state[i]==st) { f[i]+=ans; f[i]%=MOD; return; } state[size]=st; f[size]=ans; next[size]=head[h]; head[h]=size++; } }hm[2]; void decode(int *code,int m,long long st) { num=st&63; st>>=6; for(int i=m;i>=0;i--) { code[i]=st&7; st>>=3; } } long long encode(int *code,int m)//最小表示法 { int cnt=1; memset(ch,-1,sizeof(ch)); ch[0]=0; long long st=0; for(int i=0;i<=m;i++) { if(ch[code[i]]==-1)ch[code[i]]=cnt++; code[i]=ch[code[i]]; st<<=3; st|=code[i]; } st<<=6; st|=num; return st; } void shift(int *code,int m) { for(int i=m;i>0;i--)code[i]=code[i-1]; code[0]=0; } void dpblank(int i,int j,int cur) { int k,left,up; for(k=0;k<hm[cur].size;k++) { decode(code,M,hm[cur].state[k]); left=code[j-1]; up=code[j]; if(left&&up) { if(left==up) { if(num>=K)continue; int t=0; //要避免环套环的情况,需要两边插头数为偶数 for(int p=0;p<j-1;p++) if(code[p])t++; if(t&1)continue; if(num<K) { num++; code[j-1]=code[j]=0; hm[cur^1].push(encode(code,j==M?M-1:M),hm[cur].f[k]); } } else { code[j-1]=code[j]=0; for(int t=0;t<=M;t++) if(code[t]==up) code[t]=left; hm[cur^1].push(encode(code,j==M?M-1:M),hm[cur].f[k]); } } else if(left||up) { int t; if(left)t=left; else t=up; if(maze[i][j+1]) { code[j-1]=0; code[j]=t; hm[cur^1].push(encode(code,M),hm[cur].f[k]); } if(maze[i+1][j]) { code[j]=0; code[j-1]=t; hm[cur^1].push(encode(code,j==M?M-1:M),hm[cur].f[k]); } } else { if(maze[i][j+1]&&maze[i+1][j]) { code[j-1]=code[j]=13; hm[cur^1].push(encode(code,j==M?M-1:M),hm[cur].f[k]); } } } } void dpblock(int i,int j,int cur) { int k; for(k=0;k<hm[cur].size;k++) { decode(code,M,hm[cur].state[k]); code[j-1]=code[j]=0; hm[cur^1].push(encode(code,j==M?M-1:M),hm[cur].f[k]); } } char str[20]; void init() { scanf("%d%d%d",&N,&M,&K); memset(maze,0,sizeof(maze)); for(int i=1;i<=N;i++) { scanf("%s",&str); for(int j=1;j<=M;j++) if(str[j-1]=='.') maze[i][j]=1; } } void solve() { int i,j,cur=0; hm[cur].init(); hm[cur].push(0,1); for(i=1;i<=N;i++) for(j=1;j<=M;j++) { hm[cur^1].init(); if(maze[i][j])dpblank(i,j,cur); else dpblock(i,j,cur); cur^=1; } int ans=0; for(i=0;i<hm[cur].size;i++) if(hm[cur].state[i]==K) { ans+=hm[cur].f[i]; ans%=MOD; } printf("%d\n",ans); } int main() { //freopen("in.txt","r",stdin); //freopen("out.txt","w",stdout); int T; scanf("%d",&T); while(T--) { init(); solve(); } return 0; } /* Sample Input 2 4 4 1 **.. .... .... .... 4 4 1 .... .... .... .... Sample Output 2 6 */
/* HDU 4285 要形成刚好K条回路的方法数 要避免环套环的情况。 所以形成回路时,要保证两边的插头数是偶数 G++ 11765ms 12560K C++ 11656ms 12504K */ #include<stdio.h> #include<iostream> #include<algorithm> #include<string.h> using namespace std; const int MAXD=15; const int STATE=1000010; const int HASH=100007; const int MOD=1000000007; int N,M,K; int maze[MAXD][MAXD]; int code[MAXD]; int ch[MAXD]; struct HASHMAP { int head[HASH],next[STATE],size; long long state[STATE]; int f[STATE]; int cir[STATE];//形成的圈的个数 void init() { size=0; memset(head,-1,sizeof(head)); } void push(long long st,int ans,int _cir) { int i,h=st%HASH; for(i=head[h];i!=-1;i=next[i]) if(state[i]==st&&cir[i]==_cir) { f[i]+=ans; f[i]%=MOD; return; } state[size]=st; f[size]=ans; cir[size]=_cir; next[size]=head[h]; head[h]=size++; } }hm[2]; void decode(int *code,int m,long long st) { for(int i=m;i>=0;i--) { code[i]=st&7; st>>=3; } } long long encode(int *code,int m)//最小表示法 { int cnt=1; memset(ch,-1,sizeof(ch)); ch[0]=0; long long st=0; for(int i=0;i<=m;i++) { if(ch[code[i]]==-1)ch[code[i]]=cnt++; code[i]=ch[code[i]]; st<<=3; st|=code[i]; } return st; } void shift(int *code,int m) { for(int i=m;i>0;i--)code[i]=code[i-1]; code[0]=0; } void dpblank(int i,int j,int cur) { int k,left,up; for(k=0;k<hm[cur].size;k++) { decode(code,M,hm[cur].state[k]); left=code[j-1]; up=code[j]; if(left&&up) { if(left==up) { if(hm[cur].cir[k]>=K)continue; int t=0; for(int p=0;p<j-1;p++) if(code[p])t++; if(t&1)continue; if(hm[cur].cir[k]<K) { code[j-1]=code[j]=0; hm[cur^1].push(encode(code,j==M?M-1:M),hm[cur].f[k],hm[cur].cir[k]+1); } } else { code[j-1]=code[j]=0; for(int t=0;t<=M;t++) if(code[t]==up) code[t]=left; hm[cur^1].push(encode(code,j==M?M-1:M),hm[cur].f[k],hm[cur].cir[k]); } } else if(left||up) { int t; if(left)t=left; else t=up; if(maze[i][j+1]) { code[j-1]=0; code[j]=t; hm[cur^1].push(encode(code,M),hm[cur].f[k],hm[cur].cir[k]); } if(maze[i+1][j]) { code[j]=0; code[j-1]=t; hm[cur^1].push(encode(code,j==M?M-1:M),hm[cur].f[k],hm[cur].cir[k]); } } else { if(maze[i][j+1]&&maze[i+1][j]) { code[j-1]=code[j]=13; hm[cur^1].push(encode(code,j==M?M-1:M),hm[cur].f[k],hm[cur].cir[k]); } } } } void dpblock(int i,int j,int cur) { int k; for(k=0;k<hm[cur].size;k++) { decode(code,M,hm[cur].state[k]); code[j-1]=code[j]=0; hm[cur^1].push(encode(code,j==M?M-1:M),hm[cur].f[k],hm[cur].cir[k]); } } char str[20]; void init() { scanf("%d%d%d",&N,&M,&K); memset(maze,0,sizeof(maze)); for(int i=1;i<=N;i++) { scanf("%s",&str); for(int j=1;j<=M;j++) if(str[j-1]=='.') maze[i][j]=1; } } void solve() { int i,j,cur=0; hm[cur].init(); hm[cur].push(0,1,0); for(i=1;i<=N;i++) for(j=1;j<=M;j++) { hm[cur^1].init(); if(maze[i][j])dpblank(i,j,cur); else dpblock(i,j,cur); cur^=1; } int ans=0; for(i=0;i<hm[cur].size;i++) if(hm[cur].cir[i]==K) { ans+=hm[cur].f[i]; ans%=MOD; } printf("%d\n",ans); } int main() { // freopen("in.txt","r",stdin); // freopen("out.txt","w",stdout); int T; scanf("%d",&T); while(T--) { init(); solve(); } return 0; } /* Sample Input 2 4 4 1 **.. .... .... .... 4 4 1 .... .... .... .... Sample Output 2 6 */
上面各题的代码,我都是用一样的模板写的,写的风格都是一致的。插头DP写起来比较难。而且容易写错。更加复杂的插头DP真的很难想到。
但是插头DP还是很美妙的。更多插头DP的题目等我做了会更新上去的。
持续更新中......
2012-10-2 by kuangbin
posted on 2012-10-02 15:10 kuangbin 阅读(19814) 评论(1) 编辑 收藏 举报