状压dp
bzoj3591 最长上升子序列(!!!)
题目大意:求1~n的排列满足其中一个最长上升子序列是给定数列的个数。
思路:考虑nlogn单调栈求lis,可以用gi[i][j]表示栈中状态是i的时候加入j之后栈的状态,可以n^2*2^n预处理。然后考虑dp,fi[i]表示状态是i的dp值,i是个三进制数,0表示没选、1表示选了但不在栈中、2表示在栈中,三进制不好保存,如果分解会tle,所以可以表示成两个二进制数相加,用之前012的意义比较好表示。
注意:(1)选j的时候,如果j在给定的上升子序列中,要考虑j之前的数要选的要求;
(2)答案是所有栈中元素为m的和,因为可能某次转移的时候,给定的数就不在栈中了。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define N 15 #define M 14348907 using namespace std; int gi[N+1][1<<N],fi[M],mi[N+1],ai[1<<N]={0},pre[N],bi[1<<N]={0}; int main(){ int i,j,k,p,x,n,m,ans=0; scanf("%d%d",&n,&m); for (i=0;i<n;++i) pre[i]=-1; for (mi[0]=i=1;i<=n;++i) mi[i]=mi[i-1]*3; for (x=-1,i=1;i<=m;++i){ scanf("%d",&j); pre[j-1]=x;x=j-1; }for (i=0;i<(1<<n);++i) for (j=0;j<n;++j){ if ((i>>j)&1){ai[i]+=mi[j];++bi[i];continue;} for (p=i,k=0;k<n;++k) if (((i>>k)&1)&&k>j){ p^=1<<k; break; } p^=1<<j; gi[j][i]=p; } for (i=0;i<n;++i) if (pre[i]<0) fi[mi[i]<<1]=1; for (i=1;i<(1<<n);++i) for (j=0;j<n;++j){ if ((i>>j)&1) continue; if (pre[j]>=0&&(!((i>>pre[j])&1))) continue; for (k=i;k;k=(k-1)&i) if (fi[ai[i]+ai[k]]) fi[ai[i|(1<<j)]+ai[gi[j][k]]]+=fi[ai[i]+ai[k]]; } for(i=1;i<(1<<n);++i) if(bi[i]==m) ans+=fi[ai[(1<<n)-1]+ai[i]]; printf("%d\n",ans); }
轮廓线dp
bzoj4572 围棋(!!!)
题目大意:已知一些模板(2行c列),对于每种模板,求n*m的棋盘中出现这种模板的棋盘的数量。
思路:fi[i][j][k][x][y]表示轮廓线到(i,j),k表示轮廓线上的以这个点结尾能否和第一行模板匹配,x、y表示和第一二行模板匹配的kmp到的位置。预处理模板的kmp和每行在i的位置加入一个字母j转移到的gi[i][j]。除了两行都能匹配的状态,其他都可以转移,最后答案就是3^(n*m)-sigma(k=0~(2^m-1),x=0~c,y=0~c)fi[n][m][k][x][y]。
注意:1)判断两行都匹配的时候,j还要>=c,否则可能出现换行之后匹配一部分的情况。
2)对kmp要有正确的理解,表示的是匹配的长度,也就是说前i-1位是匹配的,第i位不一定匹配。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define N 105 #define M 12 #define up 7 #define p 1000000007LL #define LL long long using namespace std; int chin(){ char ch=getchar(); while(ch<'A'||ch>'Z') ch=getchar(); return (ch=='B' ? 2 : (ch=='W')); } int n,m,c,ai[M],gi[2][M][M],hi[M],fi[2][1<<M][up][up]; void add(int &x,int y){x+=y;if (x>=p) x-=p;} void init(int x){ int i,j,k;memset(ai,127,sizeof(ai)); for (i=0;i<c;++i) ai[i]=chin(); hi[0]=hi[1]=0; for (i=1;i<c;++i){ j=hi[i]; while(j&&ai[i]!=ai[j]) j=hi[j]; hi[i+1]=(ai[i]==ai[j] ? ++j : 0); }for (i=0;i<=c;++i) for (j=0;j<=2;++j){ k=i; while(k&&ai[k]!=j) k=hi[k]; if (ai[k]==j) ++k; gi[x][i][j]=k; } } int dp(){ int i,j,k,x,y,a,nk,nx,ny,cur,la,ans; for (i=0;i<2;++i) init(i); cur=0;la=1; memset(fi[cur],0,sizeof(fi[cur])); fi[cur][0][0][0]=1; for (i=1;i<=n;++i) for (j=1;j<=m;++j){ cur^=1;la^=1; memset(fi[cur],0,sizeof(fi[cur])); for (k=0;k<(1<<m);++k) for (x=0;x<=c;++x) for (y=0;y<=c;++y){ if (!fi[la][k][x][y]) continue; for (a=0;a<=2;++a){ nk=(k<<1)&((1<<m)-1); nx=gi[0][x][a]; ny=gi[1][y][a]; if (nx==c) nk^=1; if (j>=c&&ny==c&&((k>>(m-1))&1)) continue; add(fi[cur][nk][nx][ny],fi[la][k][x][y]); } } }for (ans=1,i=n*m;i;--i) ans=(int)(3LL*ans%p); for (k=0;k<(1<<m);++k) for (x=0;x<=c;++x) for (y=0;y<=c;++y) add(ans,p-fi[cur][k][x][y]); return ans; } int main(){ int q; scanf("%d%d%d%d",&n,&m,&c,&q); while(q--) printf("%d\n",dp()); }
插头dp
对于网格中的dp可以用轮廓线,如果有一些格子不能走就可以用插头dp了。
bzoj2331 地板
题目大意:用L型铺地n*m,有一些格子不能铺,求方案数。
思路:fi[i][j][s]表示铺到(i,j),轮廓线状态s,0表示没有插头,1表示插头没拐弯,2表示插头拐弯了,手动枚举转移。
注意:(1)按四进制好写;
(2)因为实际状态和四进制的差很多,所以用hash表来存储,防止mle和tle,同时使用滚动数组。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define N 105 #define M 200000 #define uu 50000 #define pp 20110520 using namespace std; int ai[N][N],fi[2][M]={0},point[uu],next[M],en[M],ha[2][M],tot[2],bt[N], nm,n,m,cur,la; int in(){ char ch=getchar(); while(ch!='_'&&ch!='*') ch=getchar(); return ch=='*';} void add(int s,int gi){ int i,x=s%uu; for (i=point[x];i;i=next[i]) if (ha[cur][en[i]]==s){ fi[cur][en[i]]=(fi[cur][en[i]]+gi)%pp; return; } ha[cur][++tot[cur]]=s;fi[cur][tot[cur]]=gi; next[++nm]=point[x];point[x]=nm;en[nm]=tot[cur];} void dp(){ int i,j,k,p,q,s,gi,ji;cur=1;la=0; tot[cur]=1;en[1]=0;ji=3; ha[cur][1]=0;fi[cur][1]=1; for (i=0;i<N;++i) bt[i]=i<<1; for (i=1;i<=n;++i){ for (j=1;j<=tot[cur];++j) ha[cur][j]=(ha[cur][j]<<2)&((1<<bt[m+1])-1); for (j=1;j<=m;++j){ cur^=1;la^=1; tot[cur]=nm=0; memset(fi[cur],0,sizeof(fi[cur])); memset(point,0,sizeof(point)); for (k=tot[la];k;--k){ s=ha[la][k];gi=fi[la][k]; if (!gi) continue; p=(s>>bt[j-1])&ji; q=(s>>bt[j])&ji; if (ai[i][j]){ if (!p&&!q) add(s,gi); }else if (!p&&!q){ if (!ai[i+1][j]) add(s+(1<<bt[j-1]),gi); if (!ai[i][j+1]) add(s+(1<<bt[j]),gi); if (!ai[i+1][j]&&!ai[i][j+1]) add(s+(1<<(bt[j-1]+1))+(1<<(bt[j]+1)),gi); }else if (!p){ s-=(1<<bt[j])*q; if (q==1){ if (!ai[i][j+1]) add(s+(1<<(bt[j]+1)),gi); if (!ai[i+1][j]) add(s+(1<<bt[j-1]),gi); }else{ add(s,gi); if (!ai[i+1][j]) add(s+(1<<(bt[j-1]+1)),gi); } }else if (!q){ s-=(1<<bt[j-1])*p; if (p==1){ if (!ai[i][j+1]) add(s+(1<<bt[j]),gi); if (!ai[i+1][j]) add(s+(1<<(bt[j-1]+1)),gi); }else{ add(s,gi); if (!ai[i][j+1]) add(s+(1<<(bt[j]+1)),gi); } }else if (p==1&&q==1){ s-=(1<<bt[j-1])+(1<<bt[j]); add(s,gi); } } } } } int main(){ int i,j;scanf("%d%d",&n,&m); memset(ai,127,sizeof(ai)); if (n<m){ for (i=1;i<=n;++i) for (j=1;j<=m;++j) ai[j][i]=in(); swap(n,m); }else for (i=1;i<=n;++i) for (j=1;j<=m;++j) ai[i][j]=in(); dp();printf("%d\n",fi[cur][1]); }
bzoj1187 神奇游乐园
题目大意:在n*m的网格中选一条回路,使权值和最大。
思路:状态同上,用括号序列表示法的三进制(0表示没有,1表示左括号,2表示右括号)。
注意:(1)每个位置的0都是0;
(2)在这个格子是左插头是左括号、上插头是右括号的时候说明在这个点形成回路(如果s中还有其他插头说明有多个回路,状态不合法,不能更新答案)。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define N 105 #define M 10 #define uu 20000 #define ji 3 using namespace std; int ai[N][M],fi[2][uu],bt[M],n,m,cur,la,ans,inf,gi[uu],pre[uu][M],suc[uu][M]; bool vi[uu]={false}; inline void add(int s,int g){fi[cur][s]=max(fi[cur][s],g);} void pree(){ int i,j,k; memset(pre,0,sizeof(pre)); memset(suc,0,sizeof(suc)); for (i=(1<<bt[m+1]);i>=0;--i){ gi[0]=0; for (j=m+1;j;--j){ k=(i>>bt[j-1])&3; if (k==3) break; if (k==2) gi[++gi[0]]=j; if (k==1){ if (!gi[0]) break; suc[i][j]=gi[gi[0]]; pre[i][gi[gi[0]]]=j; --gi[0]; } }if (!j&&!gi[0]) vi[i]=true; } } void dp(){ int i,j,k,s,g,p,q; for (i=0;i<M;++i) bt[i]=i<<1; cur=1;la=0;pree(); memset(fi[cur],-60,sizeof(fi[cur])); inf=ans=fi[cur][0];fi[cur][0]=0; for (i=1;i<=n;++i){ memset(gi,-60,sizeof(gi)); for (j=(1<<bt[m+1])-1;j>=0;--j) gi[(j<<2)&((1<<bt[m+1])-1)]=fi[cur][j]; for (j=(1<<bt[m+1])-1;j>=0;--j) fi[cur][j]=gi[j]; for (j=1;j<=m;++j){ cur^=1;la^=1; memset(fi[cur],-60,sizeof(fi[cur])); fi[cur][0]=0; for (k=(1<<bt[m+1])-1;k>=0;--k){ if (!vi[k]||fi[la][k]<=inf) continue; s=k;g=fi[la][k]; p=(s>>bt[j-1])&ji;q=(s>>bt[j])&ji; s-=(1<<bt[j-1])*p+(1<<bt[j])*q; if (!p){ if (!q){ if (s) add(s,g); add(s+(1<<bt[j-1])+(1<<(bt[j]+1)),g+ai[i][j]); }if (q==1){ add(s+(1<<bt[j-1]),g+ai[i][j]); add(s+(1<<bt[j]),g+ai[i][j]); }if (q==2){ add(s+(1<<(bt[j-1]+1)),g+ai[i][j]); add(s+(1<<(bt[j]+1)),g+ai[i][j]); } }if (p==1){ if (!q){ add(s+(1<<bt[j-1]),g+ai[i][j]); add(s+(1<<bt[j]),g+ai[i][j]); }if (q==1) add(s-(1<<bt[suc[k][j+1]-1]),g+ai[i][j]); if (q==2){ if (s) continue; ans=max(ans,g+ai[i][j]); } }if (p==2){ if (!q){ add(s+(1<<(bt[j-1]+1)),g+ai[i][j]); add(s+(1<<(bt[j]+1)),g+ai[i][j]); }if (q==1) add(s,g+ai[i][j]); if (q==2) add(s+(1<<bt[pre[k][j]-1]),g+ai[i][j]); } } } } } int main(){ int i,j;scanf("%d%d",&n,&m); for (i=1;i<=n;++i) for (j=1;j<=m;++j) scanf("%d",&ai[i][j]); dp();printf("%d\n",ans); }
bzoj3125 CITY
题目大意:n*m的网格中有四种格子:不能过、左右过、上下过、能过,问一条回路走过所有非不能过的格子的方案数。
思路:状态同上。
注意:能更新答案的地方是最后一个非不能过的格子。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define N 15 #define uu 2000000 #define pp 50000 #define LL long long using namespace std; int ha[2][uu],tot[2],next[uu],point[pp]={0},cur,la,nm,ai[N][N],n,m,bt[N],xx,yy; LL fi[2][uu]; inline int in(){ char ch=getchar(); while(ch!='.'&&ch!='-'&&ch!='|'&&ch!='#') ch=getchar(); if (ch=='.') return 1; if (ch=='-') return 2; if (ch=='|') return 3; return 0;} inline void add(int s,LL d){ int i,x=s%pp; for (i=point[x];i;i=next[i]) if (ha[cur][i]==s){fi[cur][i]+=d;return;} ha[cur][++nm]=s;fi[cur][nm]=d; next[nm]=point[x];point[x]=nm; } inline bool cg(int i,int j,int x){ if (x==2) return ((ai[i][j]==1||ai[i][j]==2)&&(ai[i][j+1]==1||ai[i][j+1]==2)); if (x==3) return ((ai[i][j]==1||ai[i][j]==3)&&(ai[i+1][j]==1||ai[i+1][j]==3)); } void dp(){ int i,j,k,p,q,s,ji,po,cn,cc;LL g; cur=1;la=0;tot[cur]=1; ha[cur][1]=0;fi[cur][1]=1;ji=3; for (i=0;i<N;++i) bt[i]=i<<1; for (i=1;i<=n;++i){ for (j=tot[cur];j;--j) ha[cur][j]=(ha[cur][j]<<2)&((1<<bt[m+1])-1); for (j=1;j<=m;++j){ cur^=1;la^=1;nm=0; memset(point,0,sizeof(point)); for (k=tot[la];k;--k){ s=ha[la][k];g=fi[la][k];fi[la][k]=0; if (!g) continue; p=(s>>bt[j-1])&ji; q=(s>>bt[j])&ji; s-=(1<<bt[j-1])*p+(1<<bt[j])*q; if (p==0){ if (q==0){ if (!ai[i][j]) add(s,g); if (cg(i,j,3)&&cg(i,j,2)) add(s+(1<<bt[j-1])+(1<<(bt[j]+1)),g); }if (q==1){ if (cg(i,j,2)) add(s+(1<<bt[j]),g); if (cg(i,j,3)) add(s+(1<<bt[j-1]),g); }if (q==2){ if (cg(i,j,2)) add(s+(1<<(bt[j]+1)),g); if (cg(i,j,3)) add(s+(1<<(bt[j-1]+1)),g); } }if (p==1){ if (q==0){ if (cg(i,j,2)) add(s+(1<<bt[j]),g); if (cg(i,j,3)) add(s+(1<<bt[j-1]),g); }if (q==1){ for (cn=0,po=j+2;po<=m+1;++po){ cc=(s>>bt[po-1])&ji; if (!cc) continue; if (cc==1) ++cn; if (cc==2){if (!cn) break;--cn;} }add(s-(1<<bt[po-1]),g); }if (q==2){ if (!s&&i==xx&&j==yy) add(s,g); } }if (p==2){ if (q==0){ if (cg(i,j,2)) add(s+(1<<(bt[j]+1)),g); if (cg(i,j,3)) add(s+(1<<(bt[j-1]+1)),g); }if (q==1) add(s,g); if (q==2){ for (cn=0,po=j-1;po>0;--po){ cc=(s>>bt[po-1])&ji; if (!cc) continue; if (cc==2) ++cn; if (cc==1){if (!cn) break;--cn;} }add(s+(1<<bt[po-1]),g); } } }tot[cur]=nm; } } } int main(){ int i,j;scanf("%d%d",&n,&m); memset(ai,0,sizeof(ai)); for (i=1;i<=n;++i) for (j=1;j<=m;++j){ ai[i][j]=in(); if (ai[i][j]){xx=i;yy=j;} } dp();printf("%I64d\n",fi[cur][1]); }
bzoj3814 简单回路
题目大意:给n*m有障碍网格,q组询问,求经过(x,y)和(x+1,y)之间边的简单回路个数。
思路:正反做两遍插头dp,对于询问就是合并从上面和从下面过来的这一行更新完之后的插头,可以暴力预处理上下能匹配成简单回路的插头对。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define N 1005 #define M 20000 #define up 4100 #define uu 8 #define pp 1000000007LL #define LL long long #define ji 3 using namespace std; struct use{ int x,y,p; bool operator<(const use&xx)const{return (x==xx.x ? y<xx.y : x<xx.x);} }ask[M]; int pt=0,pa[N][2],pre[M][uu],suc[M][uu],bt[uu],ai[2][N][uu],n,m,cur,la,qq,zh[uu], ct[M]={0},zr[M]={0}; LL fi[2][M],gg[M],gi[2][N][up],as[M]; bool vi[M]={false},vv[uu]; bool judge(int x,int y){ int i,a,b; for (i=1;i<=m;++i){ a=(((x>>bt[i-1])&ji)>0); b=(((y>>bt[i-1])&ji)>0); if (a^b) return false; }for (i=1;i<=m;++i) if ((x>>bt[i-1])&ji) break; memset(vv,false,sizeof(vv)); for (;;){ vv[i]=true; if (((x>>bt[i-1])&ji)==1) i=suc[x][i]; else i=pre[x][i]; vv[i]=true; if (((y>>bt[i-1])&ji)==1) i=suc[y][i]; else i=pre[y][i]; if (vv[i]) break; }for (i=1;i<=m;++i) if (((x>>bt[i-1])&ji)&&!vv[i]) break; if (i<=m) return false; return true;} int zher(int x){ int i,y=0; for (i=1;i<=m;++i) if ((x>>bt[m-i])&ji) y|=(1<<bt[i-1])*(3-((x>>bt[m-i])&ji)); return y;} void pree(){ int i,j,x,zt; for (i=0;i<uu;++i) bt[i]=i<<1; for (i=(1<<bt[m+1])-1;i>=0;--i){ for (zt=0,j=1;j<=m+1;++j){ x=(i>>bt[j-1])&ji; if (x>2) break; if (!x) continue;++ct[i]; if (x==1) zh[++zt]=j; else{ if (!zt) break; pre[i][j]=zh[zt]; suc[i][zh[zt]]=j; --zt; } }if (j>m+1&&!zt) vi[i]=true; }for (i=(1<<bt[m])-1;i;--i){ if (!vi[i]) continue; zr[i]=zher(i); for (j=(1<<bt[m])-1;j;--j){ if (!vi[j]) continue; if (ct[i]==ct[j]&&judge(i,j)){ pa[++pt][0]=i;pa[pt][1]=j; } } } } void add(int x,LL y){fi[cur][x]=(fi[cur][x]+y)%pp;} void dp(int kk){ int i,j,k,s,p,q;LL g;cur=1;la=0; memset(fi,0,sizeof(fi)); fi[cur][0]=1LL; for (i=1;i<=n;++i){ for (j=(1<<bt[m+1])-1;j>=0;--j) gg[(j<<2)&((1<<bt[m+1])-1)]=fi[cur][j]; for (j=(1<<bt[m])-1;j>=0;--j){ if (!kk) gi[kk][i-1][j]=fi[cur][j]; else gi[kk][n-i+1][zr[j]]=fi[cur][j]; }for (j=(1<<bt[m+1])-1;j>=0;--j) fi[cur][j]=gg[j]; for (j=1;j<=m;++j){ cur^=1;la^=1; fi[cur][0]=1; for (k=(1<<bt[m+1])-1;k>=0;--k){ if (!vi[k]||!fi[la][k]) continue; s=k;g=fi[la][k];fi[la][k]=0LL; p=(k>>bt[j-1])&ji; q=(k>>bt[j])&ji; s-=(1<<bt[j-1])*p+(1<<bt[j])*q; if (!p){ if (!q){ if (s) add(s,g); if (ai[kk][i][j]&&ai[kk][i+1][j]&&ai[kk][i][j+1]) add(s+(1<<bt[j-1])+(1<<(bt[j]+1)),g); }if (q==1){ if (ai[kk][i+1][j]) add(s+(1<<bt[j-1]),g); if (ai[kk][i][j+1]) add(s+(1<<bt[j]),g); }if (q==2){ if (ai[kk][i+1][j]) add(s+(1<<(bt[j-1]+1)),g); if (ai[kk][i][j+1]) add(s+(1<<(bt[j]+1)),g); } }if (p==1){ if (!q){ if (ai[kk][i+1][j]) add(s+(1<<bt[j-1]),g); if (ai[kk][i][j+1]) add(s+(1<<bt[j]),g); }if (q==1) add(s-(1<<bt[suc[k][j+1]-1]),g); if (q==2) continue; }if (p==2){ if (!q){ if (ai[kk][i+1][j]) add(s+(1<<(bt[j-1]+1)),g); if (ai[kk][i][j+1]) add(s+(1<<(bt[j]+1)),g); }if (q==1) add(s,g); if (q==2) add(s+(1<<bt[pre[k][j]-1]),g); } } } } } void geta(){ int i,j,k,qi=1;LL ans; for (i=1;i<n;++i) for (j=1;j<=m;++j) for(;qi<=qq&&ask[qi].x==i&&ask[qi].y==j;++qi){ for (ans=0LL,k=1;k<=pt;++k) if ((pa[k][0]>>bt[j-1])&ji) ans=(ans+gi[0][i][pa[k][0]]*gi[1][i][pa[k][1]]%pp)%pp; as[ask[qi].p]=ans; } } int main(){ int k,i,j,x,y; memset(ai,0,sizeof(ai)); scanf("%d%d%d",&n,&m,&k); for (i=1;i<=n;++i) for (j=1;j<=m;++j) ai[0][i][j]=ai[1][i][j]=1; for (i=1;i<=k;++i){ scanf("%d%d",&x,&y); ai[0][x][y]=ai[1][n-x+1][m-y+1]=0; }scanf("%d",&qq); for (i=1;i<=qq;++i){ scanf("%d%d",&x,&y); ask[i]=(use){x,y,i}; }sort(ask+1,ask+qq+1); pree();dp(0);dp(1);geta(); for (i=1;i<=qq;++i) printf("%I64d\n",as[i]); }