插头dp
感受:
我觉得重点是理解,算法并不是直接想出怎样由一种方案变成另一种方案。而是方案本来就在那里,我们只是枚举状态统计了答案。
看看cdq的讲义什么的,一开始可能觉得状态很多,但其实灰常简单
就像lyd说的,考插头dp的题目就是在考模板2333
(学这个之前连hash_map都没写过2333
WA:
(1) 初始化矩阵,周围格子有可能是0--->转移出错
(2)统计答案最后统计的是合法的,即st==0的。。。
题目集锦:
(1)cojs1512 经过所有可经过的点的一条回路个数
因为是一条回路,依次dp每个点的状态,所以记录endx,endy只在终点更新答案,其它点的闭合回路不计算。
#include<bits/stdc++.h> using namespace std; int read(){ int x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();} return x*f; } #define mod 13131 #define N 4500 #define ll long long struct dp_hash{ int head[mod],next[N],sz; ll f[N],st[N]; void init(){ memset(head,0,sizeof(head));sz=0; } void push(ll S,ll ins){ int now=S%mod; for(int i=head[now];i;i=next[i]) if(st[i]==S){ f[i]+=ins;return; } sz++; f[sz]=ins;st[sz]=S; next[sz]=head[now]; head[now]=sz; } }dp[2]; ll ans=0; int n,m,code[16],ch[16],a[16][16],cur,Endx,Endy; char s[15]; void Decode(ll S){ for(int i=m;i>=0;i--){ code[i]=S&7; S>>=3; } } ll Encode(){ ll S=0; memset(ch,-1,sizeof(ch));int cnt=0; ch[0]=0; for(int i=0;i<=m;i++){ if(ch[code[i]]==-1)ch[code[i]]=++cnt; code[i]=ch[code[i]]; S<<=3;S|=code[i]; } return S; } void Shift(){ for(int i=m;i>0;i--)code[i]=code[i-1]; code[0]=0; } void DP(int x,int y,int pre,bool type){ if(!type){ for(int k=1;k<=dp[pre].sz;k++){ Decode(dp[pre].st[k]); if(y==m)Shift(); dp[pre^1].push(Encode(),dp[pre].f[k]); } return; } for(int k=1;k<=dp[pre].sz;k++){ Decode(dp[pre].st[k]); int Left=code[y-1],Up=code[y]; if(Left&&Up){ if(Left==Up){ if(Endx==x&&Endy==y) ans+=dp[cur].f[k]; } else{ code[y]=code[y-1]=0; for(int i=0;i<=m;i++) if(code[i]==Up)code[i]=Left; if(y==m)Shift(); dp[pre^1].push(Encode(),dp[pre].f[k]); } } else if(Left==0&&Up==0){ if(a[x][y+1]&&a[x+1][y]){ code[y-1]=code[y]=15; dp[pre^1].push(Encode(),dp[pre].f[k]); } } else{ int tmp=Left==0?Up:Left; if(a[x][y+1]){ code[y-1]=0;code[y]=tmp; if(y==m)Shift(); dp[pre^1].push(Encode(),dp[pre].f[k]); } if(a[x+1][y]){ code[y-1]=tmp;code[y]=0; if(y==m)Shift(); dp[pre^1].push(Encode(),dp[pre].f[k]); } } } } int main(){ freopen("formula1.in","r",stdin); freopen("formula1.out","w",stdout); n=read();m=read(); for(int i=1;i<=n;i++){ scanf("%s",s+1); for(int j=1;j<=m;j++) if(s[j]!='*'){ a[i][j]=1; Endx=i;Endy=j; } } if(Endx==0){ puts("0");return 0; } cur=0; dp[cur].init(); dp[cur].push(0,1); for(int i=1;i<=n;i++) for(int j=1;j<=m;j++){ dp[cur^1].init(); DP(i,j,cur,a[i][j]); cur^=1; } printf("%lld\n",ans); return 0; }
(2) hdu1693 Eat The Trees
经过所有非障碍点的回路个数(不限条数)。
和上一道题的区别就是非终点的回路也要更新其它状态。
#include<bits/stdc++.h> using namespace std; int read(){ int x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();} return x*f; } #define N 1000000 #define mod 13131 #define ll long long struct dphash{ int head[N],next[N],sz; ll st[N],f[N]; void init(){ memset(head,0,sizeof(head));sz=0; } void push(ll S,ll ins){ int now=S%mod; for(int i=head[now];i;i=next[i]) if(st[i]==S){ f[i]+=ins;return; } sz++; st[sz]=S;f[sz]=ins; next[sz]=head[now];head[now]=sz; } }dp[2]; int n,m,T,cur,pw[16],a[16][16],code[16]; ll ans; ll Encode(){ ll S=0; memset(pw,-1,sizeof(pw)); pw[0]=0;int cnt=0; for(int i=0;i<=m;i++){ if(pw[code[i]]==-1)pw[code[i]]=++cnt; code[i]=pw[code[i]]; S<<=3;S|=code[i]; } return S; } void Decode(ll S){ for(int i=m;i>=0;i--){ code[i]=S&7; S>>=3; } } void Shift(){ for(int i=m;i;i--)code[i]=code[i-1]; code[0]=0; } void DP(int x,int y,int pre,bool type){ if(!type){ for(int i=1;i<=dp[pre].sz;i++){ Decode(dp[pre].st[i]); // cout<<"code "<<code[y]<<' '<<code[y-1]<<endl; code[y]=code[y-1]=0; if(y==m)Shift(); dp[pre^1].push(Encode(),dp[pre].f[i]); } return; } for(int i=1;i<=dp[pre].sz;i++){ Decode(dp[pre].st[i]); int Left=code[y-1],Up=code[y]; if(Left&&Up){ code[y]=code[y-1]=0; for(int j=0;j<=m;j++)if(code[j]==Up)code[j]=Left; if(y==m)Shift(); dp[pre^1].push(Encode(),dp[pre].f[i]); } else if(Left==0&&Up==0){ if(a[x][y+1]&&a[x+1][y]){ code[y-1]=code[y]=15; dp[pre^1].push(Encode(),dp[pre].f[i]); } } else{ int tmp=Left==0?Up:Left; if(a[x][y+1]){ code[y]=tmp;code[y-1]=0; dp[pre^1].push(Encode(),dp[pre].f[i]); } if(a[x+1][y]){ code[y-1]=tmp;code[y]=0; if(y==m)Shift(); dp[pre^1].push(Encode(),dp[pre].f[i]); } } } } int main(){ // freopen("1693.in","r",stdin); // freopen("1693.out","w",stdout); T=read(); for(int k=1;k<=T;k++){ memset(a,0,sizeof(a)); n=read();m=read(); for(int i=1;i<=n;i++) for(int j=1;j<=m;j++)a[i][j]=read(); cur=0; dp[cur].init(); dp[cur].push(0,1); for(int i=1;i<=n;i++) for(int j=1;j<=m;j++){ dp[cur^1].init(); DP(i,j,cur,a[i][j]); cur^=1; } ans=0; for(int i=dp[cur].head[0];i;i=dp[cur].next[i])if(dp[cur].st[i]==0)ans+=dp[cur].f[i]; printf("Case %d: There are %I64d ways to eat the trees.\n",k,ans); } return 0; }
(3)[国家集训队2011]画圈圈
根据射线法,判断一个点左边的下插头奇偶性判断是否在回路内。
#include<bits/stdc++.h> using namespace std; int read(){ int x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();} return x*f; } #define ll long long #define N 1000000 #define mod 13131 #define MOD 123456791 struct dp_hash{ int head[mod],next[N],sz; ll f[N],st[N]; void init(){ memset(head,0,sizeof(head));sz=0; } void push(ll S,ll ins){ ins%=MOD;int now=S%mod; for(int i=head[now];i;i=next[i]) if(st[i]==S){ (f[i]+=ins)%=MOD;return; } sz++;next[sz]=head[now];head[now]=sz; st[sz]=S;f[sz]=ins; } }dp[2]; int n,m,cur,pw[16],a[25][16],code[16]; ll ans; char s[16]; void Decode(ll S){ for(int i=m;i>=0;i--){ code[i]=S&7; S>>=3; } } ll Encode(){ ll S=0; memset(pw,-1,sizeof(pw)); pw[0]=0;int cnt=0; for(int i=0;i<=m;i++){ if(pw[code[i]]==-1)pw[code[i]]=++cnt; code[i]=pw[code[i]]; S<<=3;S|=code[i]; } return S; } void Shift(){ for(int i=m;i;i--)code[i]=code[i-1]; code[0]=0; } void DP(int x,int y,int pre,int type){ if(type){ for(int i=1;i<=dp[pre].sz;i++){ Decode(dp[pre].st[i]);int t=0; for(int j=0;j<y-1;j++)if(code[j]!=0)t++; if((t%2==1&&type==1)||(t%2==0&&type==2)){ if(y==m)Shift(); dp[pre^1].push(Encode(),dp[pre].f[i]); } } return; } for(int i=1;i<=dp[pre].sz;i++){ Decode(dp[pre].st[i]); int Left=code[y-1],Up=code[y]; if(Left&&Up){ code[y]=code[y-1]=0; for(int j=0;j<=m;j++)if(code[j]==Up)code[j]=Left; if(y==m)Shift(); dp[pre^1].push(Encode(),dp[pre].f[i]); } else if(Left==0&&Up==0){ if(a[x][y+1]==0&&a[x+1][y]==0){ code[y-1]=code[y]=15; dp[pre^1].push(Encode(),dp[pre].f[i]); } } else{ int tmp=Left==0?Up:Left; if(a[x][y+1]==0){ code[y]=tmp;code[y-1]=0; dp[pre^1].push(Encode(),dp[pre].f[i]); } if(a[x+1][y]==0){ code[y-1]=tmp;code[y]=0; if(y==m)Shift(); dp[pre^1].push(Encode(),dp[pre].f[i]); } } } } int main(){ freopen("nt2011_circle.in","r",stdin); freopen("nt2011_circle.out","w",stdout); n=read();m=read(); memset(a,-1,sizeof(a)); for(int i=1;i<=n;i++){ scanf("%s",s+1); for(int j=1;j<=m;j++) if(s[j]=='.')a[i][j]=0; else if(s[j]=='*')a[i][j]=1; else a[i][j]=2; } cur=0; dp[cur].init(); dp[cur].push(0,1); for(int i=1;i<=n;i++) for(int j=1;j<=m;j++){ dp[cur^1].init(); DP(i,j,cur,a[i][j]); cur^=1; } for(int i=dp[cur].head[0];i;i=dp[cur].next[i])if(dp[cur].st[i]==0){ans=dp[cur].f[i];break;} printf("%lld\n",ans); }
(4)BZOJ1187
都是把记录方案个数的变量改为记录当前状态得到的权值
注意的是每个状态现在要记录的是最大值,就是说一个状态可以对应多种可能,你只有一个轮廓线,但轮廓线上方是怎么样的无法确定,所以要取ma]x
还有就是更新答案的时候,要code[y]=code[y-1]=0,然后判断Encode()==0,否则的话有可能两边还有插头,这样下面还会更新一次。
边界什么的也比较麻烦,容易写漏。
(5) BZOJ3753
这道我感觉超难的qaq,注意dp的是矩阵的边,但权值还是格子。。然后就会发现不会转移,奥妙重重。。
smz原来教过我,然而我忘了QAQ
然后她现在人在衡水QAQ不上QQ QAQ
问了大爷。。还没回复QAQ
还没写QAQ
update 前几天终于写了,这个其实可以理解为是否放置了下插头
首先就是n++,m++,然后选择一条边(即新图的一个格子),如果它有下插头,才能对当前格子是否在圈内产生影响。
贴代码:
#include<bits/stdc++.h> #define ll long long using namespace std; inline int read(){ int x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=10*x+ch-'0';ch=getchar();} return x*f; } #define mod 13131 #define N 1000000 #define inf 1e9 int n,m,code[20],a[15][15],v[15][15],num=0,ans=-inf; struct hash_map{ int head[mod],next[N],sz,f[N]; ll st[N]; void init(){ memset(head,0,sizeof(head));sz=0; } void push(ll S,int ins){ int now=S%mod; for(int i=head[now];i;i=next[i]) if(st[i]==S){ f[i]=max(f[i],ins);return; } st[++sz]=S;f[sz]=ins; next[sz]=head[now];head[now]=sz; } }dp[2]; int pw[20]; ll Encode(){ memset(pw,-1,sizeof(pw)); pw[0]=0;ll S=0;int cnt=0; for(int i=0;i<=m;i++){ if(pw[code[i]]==-1)pw[code[i]]=++cnt; code[i]=pw[code[i]]; S=S<<3|code[i]; } return S; } void Decode(ll S){ for(int i=m;i>=0;i--) code[i]=S&7,S>>=3; } void Shift(){ for(int i=m;i;i--)code[i]=code[i-1]; code[0]=0; } bool check(int x,int y,bool is){ if(a[x][y]==1&&is)return 0; if(a[x][y]==2&&is==0)return 0; return 1; } void DP(int x,int y,int pre){ for(int i=1;i<=dp[pre].sz;i++){ Decode(dp[pre].st[i]); if(y==1){if(code[m])continue;Shift();} int Left=code[y-1],Up=code[y]; bool is=0; for(int j=0;j<y-1;j++)if(code[j])is^=1; if(Left&&Up){ code[y]=code[y-1]=0; if(Left==Up){ if(Encode()==0&&num==0)ans=max(ans,dp[pre].f[i]); } else{ if(check(x,y,is)){ for(int j=0;j<=m;j++)if(code[j]==Up)code[j]=Left; dp[pre^1].push(Encode(),dp[pre].f[i]+is*v[x][y]); } } } else if(Left||Up){ int tmp=Left?Left:Up; if(y!=m&&check(x,y,is)){ code[y-1]=0;code[y]=tmp; dp[pre^1].push(Encode(),dp[pre].f[i]+is*v[x][y]); } is^=1; if(x!=n&&check(x,y,is)){ code[y-1]=tmp;code[y]=0; dp[pre^1].push(Encode(),dp[pre].f[i]+is*v[x][y]); } } else{ if(check(x,y,is))dp[pre^1].push(Encode(),dp[pre].f[i]+is*v[x][y]); is^=1; if(x!=n&&y!=m&&check(x,y,is)){ code[y]=code[y-1]=15; dp[pre^1].push(Encode(),dp[pre].f[i]+is*v[x][y]); } } } } int main(){ // freopen("test.in","r",stdin); n=read();m=read();n++;m++; for(int i=1;i<n;i++) for(int j=1;j<m;j++)v[i][j]=read(); int cur=0;dp[cur].init();dp[cur].push(0,0); for(int i=1;i<=n;i++) for(int j=1;j<=m;j++){ dp[cur^1].init(); DP(i,j,cur); cur^=1; } printf("%d\n",ans); for(int i=1;i<n;i++) for(int j=1;j<m;j++){ a[i][j]=read(); if(a[i][j]==2)num++; } cur=0;ans=-inf; dp[cur].init();dp[cur].push(0,0); for(int i=1;i<=n;i++) for(int j=1;j<=m;j++){ dp[cur^1].init(); DP(i,j,cur); cur^=1; if(a[i][j]==2)num--; } if(ans==-inf)printf("Can not establish GFW."); else printf("%d\n",ans); return 0; }