插头dp学习笔记
由于插头dp很难懂于是又来记笔记了
插头DP可以用来解决一些连通性状压问题。具体流程是分格子处理,然后可以根据需要进行滚动,状压一下轮廓线状态,常用4进制(?)之类的。
拿luogu例题做例子:
给出n*m的方格,有些格子不能铺线,其它格子必须铺,形成一个闭合回路。问有多少种铺法?n,m(2<=n,m<=12)
对于此题只考虑格子上方的插头和格子左方的插头情况,然后特殊的对于当前轮廓线拐角(DP位置)有b1,b2表示右插头和下插头,然后在四进制中,对于一个线段有0表示没有插头,1表示有左端点(左括号),2表示有右端点(右括号),括号互相匹配。
首先如果当前格子不能走那么只能从没有b1和b2的位置转移过来:
if(!a[i][j]){
if(!b1&&!b2){
ins(bit,num);
}
}
然后对于当前格子能走分多种情况讨论:
!b1&&!b2
对于这种情况我们要加入两个插头(一个左插头一个右插头)来使这个点被走过。
else if(!b1&&!b2){
if(a[i+1][j]&&a[i][j+1])
ins(bit+bin[j-1]+bin[j]*2,num);
}
在代码中就是加了第j条线的左括号和j+1的右括号。
!b1&&b2
只有一个插头,并且是向下的,于是可以选择把这个插头向下延伸或者向右:
else if(!b1&&b2){
if(a[i][j+1])
ins(bit,num);
if(a[i+1][j])
ins(bit-bin[j]*b2+bin[j-1]*b2,num);
}
如果能向右延伸就向右延伸,如果能向下延伸,先把第j+1条线的插头去掉,改成第j条线的向下的插头。
b1&&!b2
有横着戳过来的插头,可以继续向右延伸或者改成向下。
else if(b1&&!b2){
if(a[i+1][j])
ins(bit,num);
if(a[i][j+1])
ins(bit-bin[j-1]*b1+bin[j]*b1,num);
}
另外上面两种情况里面\(\times b1/b2\)是因为不改变它们作为左括号还是右括号的性质。
b1=1&&b2=1
两个左括号相遇合并了,我们把这两个左括号删掉,但为了保证合法,把右边原来的一个右括号改成左括号。
else if(b1==1&&b2==1){
int k1=1;
F(l,j+1,m){
if(((bit>>(l<<1))&3)==1)
k1++;
if(((bit>>(l<<1))&3)==2)
k1--;
if(!k1){
ins(bit-bin[j]-bin[j-1]-bin[l],num);
break;
}
}
}
b1=2&&b2=2
和上面类似,找到左边的左括号改成右括号。
else if(b1==2&&b2==2){
int k1=1;
D(l,j-2,0){
if(((bit>>(l<<1))&3)==1)
k1--;
if(((bit>>(l<<1))&3)==2)
k1++;
if(!k1){
ins(bit-bin[j]*2-bin[j-1]*2+bin[l],num);
break;
}
}
}
b1=2&&b2=1
两个左右括号可以直接删掉了,虽然是背靠背的但是不影响,因为一定还有剩下的左右括号配对。
else if(b1==2&&b2==1){
ins(bit-bin[j-1]*2-bin[j],num);
}
b1=1&&b2=2
说明这可以当做终点了,如果这是终点那么累加到答案里面,否则弃掉。
else if(i==ex&&j==ey)ans+=num;
然后这几个状态大概就可以处理所有情况了,放个全码:
#include<cstring>
#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
namespace EMT{
typedef long long ll;typedef double db;
#define pf printf
#define F(i,a,b) for(register int i=a;i<=b;i++)
#define D(i,a,b) for(register int i=a;i>=b;i--)
#define int long long
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=x*10+ch-'0',ch=getchar();return x*f;}
inline void file(){freopen("in.in","r",stdin);freopen("my.out","w",stdout);}
inline int max(int a,int b){return a>b?a:b;}inline int min(int a,int b){return a<b?a:b;}
inline void pi(int x){pf("%lld ",x);}inline void pn(){pf("\n");}
const int N=15,mod=300000;
int a[N][N],n,m,ex,ey,bin[N],now,lst,head[mod],next[2<<24],cnt[2],val[2][2<<24],key[2][2<<24];
inline void ins(int bit,int num){
ll v=bit%mod;
for(int i=head[v];i;i=next[i]){
if(key[now][i]==bit){
val[now][i]+=num;
return;
}
}next[++cnt[now]]=head[v];
head[v]=cnt[now];
val[now][cnt[now]]=num;
key[now][cnt[now]]=bit;
}
inline short main(){
// file();
std::cout<<std::oct;
n=read(),m=read();int ans=0;
F(i,1,n){
F(j,1,m){
char ch=getchar();
while(ch!='*'&&ch!='.')ch=getchar();
a[i][j]=(ch=='*'?0:1);
if(a[i][j])ex=i,ey=j;
}
}bin[0]=1;
F(i,1,m)bin[i]=bin[i-1]<<2;
cnt[now]=1,val[now][1]=1;
F(i,1,n){
F(j,1,cnt[now])key[now][j]<<=2;
F(j,1,m){
memset(head,0,sizeof(head));
lst=now,now^=1;
cnt[now]=0;
F(k,1,cnt[lst]){
int bit=key[lst][k],num=val[lst][k];
int b1=(bit>>((j-1)<<1))&3,b2=(bit>>(j<<1))&3;
if(!a[i][j]){
if(!b1&&!b2){
ins(bit,num);
}
}else if(!b1&&!b2){
if(a[i+1][j]&&a[i][j+1])
ins(bit+bin[j-1]+bin[j]*2,num);
}else if(!b1&&b2){
if(a[i][j+1])
ins(bit,num);
if(a[i+1][j])
ins(bit-bin[j]*b2+bin[j-1]*b2,num);
}else if(b1&&!b2){
if(a[i+1][j])
ins(bit,num);
if(a[i][j+1])
ins(bit-bin[j-1]*b1+bin[j]*b1,num);
}else if(b1==1&&b2==1){
int k1=1;
F(l,j+1,m){
if(((bit>>(l<<1))&3)==1)
k1++;
if(((bit>>(l<<1))&3)==2)
k1--;
if(!k1){
ins(bit-bin[j]-bin[j-1]-bin[l],num);
break;
}
}
}else if(b1==2&&b2==2){
int k1=1;
D(l,j-2,0){
if(((bit>>(l<<1))&3)==1)
k1--;
if(((bit>>(l<<1))&3)==2)
k1++;
if(!k1){
ins(bit-bin[j]*2-bin[j-1]*2+bin[l],num);
break;
}
}
}else if(b1==2&&b2==1){
ins(bit-bin[j-1]*2-bin[j],num);
}else if(i==ex&&j==ey)ans+=num;
}
}
}pi(ans);
return 0;
}
}
signed main(){return EMT::main();}
刷题去了
Everything that kills me makes me feel alive.