插头DP

插头DP

应用:主要用于解决一些涉及联通性和回路的问题,数据范围一般不大,状态较少用Hash表存。

  1. Ural 1519 Formula 1

考虑0/1状压,当前轮廓线上是否有插头,但是发现这样做不大可行,最后可能会合并出多条回路,原因是不知道插头上边的状态,解决的方式有两个,一是用最小表示法存联通情况,二是用括号序列的写法,个人采用了第二种。

发现如果两个插头在括号中匹配了,那么他们属于一条回路,对于一个左括号和一个右括号,只能在最后一个格子合并,其余情况分类讨论。

注意在加入一个插头的时候就判断它是否合法而不是转移用到它的时候再判,后者其实也可行但是在换行的时候需要特判,于是干脆直接用第一种。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N=15;
const int mod=20040103;
char str[N][N];
struct HA{
	struct Edg{
		int nxt,s;ll val;
	}e[1<<N];
	int idx,h[mod];
	void insert(int s,ll val){
		int x=s%mod;
		for(int i=h[x];i;i=e[i].nxt)
			if(e[i].s==s){
				e[i].val+=val;
				return ;
			}
		e[++idx].nxt=h[x];h[x]=idx;
		e[idx].s=s;e[idx].val=val;
	}
	void clear(){
		for(int i=1;i<=idx;i++)
			h[e[i].s%mod]=0;
		idx=0;
	}
}f[2];
int main(){
	int n,m,ex,ey;
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++){
		scanf("%s",str[i]+1);
		for(int j=1;j<=m;j++)
			if(str[i][j]=='.')ex=i,ey=j;
	}
	int cur=1;
	f[0].insert(0,1);
	ll res=0;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++,cur^=1){
			f[cur].clear();
			for(int k=1;k<=f[cur^1].idx;k++){
				int s=f[cur^1].e[k].s;ll val=f[cur^1].e[k].val;
				int r=s&3,up=s>>j*2&3;
				if(str[i][j]=='*'){
					if(!r&&!up)f[cur].insert(s,val);
				}else if(!r&&!up){
					if(str[i][j+1]=='.'&&str[i+1][j]=='.')
						f[cur].insert(s|2|(1<<j*2),val);
				}else if(r&&!up){
					if(str[i][j+1]=='.')
						f[cur].insert(s,val);
					if(str[i+1][j]=='.')
						f[cur].insert(s^r|(r<<j*2),val);
				}else if(up&&!r){
					if(str[i+1][j]=='.')
						f[cur].insert(s,val);
					if(str[i][j+1]=='.')
						f[cur].insert(s^(up<<j*2)|up,val);
				}else if(r==1&&up==2){
					if(i==ex&&j==ey&&(s^r^(up<<j*2))==0)
						res+=val;
				}else if(r==2&&up==1){
					f[cur].insert(s^r^(up<<j*2),val);
				}else if(r==1&&up==1){
					int nxt=s^r^(up<<j*2);
					for(int u=j+1,p=1;u<=m;u++){
						int now=nxt>>u*2&3;
						p+=(now==1)-(now==2);
						if(!p){
							nxt^=3<<u*2;
							break;
						}
					}
					f[cur].insert(nxt,val);
				}else if(r==2&&up==2){
					int nxt=s^r^(up<<j*2);
					for(int u=j-1,p=1;u;u--){
						int now=nxt>>u*2&3;
						p+=(now==2)-(now==1);
						if(!p){
							nxt^=3<<u*2;
							break;
						}
					}
					f[cur].insert(nxt,val);
				}
			}
		}
	}
	printf("%lld\n",res);
	return 0;
}

2.CITY

块的种类变多了,加入的时候分类讨论多亿点。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N=14;
const int mod=20040103;
char str[N][N];
struct HA{
	struct Edge{
		int nxt,s;
		ll val;
	}e[mod];
	int h[mod],idx;
	void insert(int s,ll v){
		int x=s%mod;
		for(int i=h[x];i;i=e[i].nxt)
			if(e[i].s==s){
				e[i].val+=v;
				return ;
			}
		e[++idx].s=s;e[idx].val=v;
		e[idx].nxt=h[x];h[x]=idx;
	}
	void clear(){
		for(int i=1;i<=idx;i++)h[e[i].s%mod]=0;
		idx=0;
	}
}f[2];
int main(){
	int n,m,ex,ey;
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++){
		scanf("%s",str[i]+1);
		for(int j=1;j<=m;j++)
			if(str[i][j]=='.'||str[i][j]=='|'||str[i][j]=='-')ex=i,ey=j;
	}
	ll res=0;
	int cur=1;
	f[0].insert(0,1);
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++,cur^=1){
			f[cur].clear();
			for(int k=1;k<=f[cur^1].idx;k++){
				int s=f[cur^1].e[k].s;ll val=f[cur^1].e[k].val;
				int r=s&3,up=s>>j*2&3;
				if(str[i][j]=='#'){
					if(!r&&!up){
						f[cur].insert(s,val);
					}
				}else if(str[i][j]=='|'){
					if(!r&&up){
						if(str[i+1][j]=='.'||str[i+1][j]=='|')f[cur].insert(s,val);
					}
				}else if(str[i][j]=='-'){
					if(r&&!up){
						if(str[i][j+1]=='.'||str[i][j+1]=='-')f[cur].insert(s,val);
					}
				}else if(!r&&!up){
					if((str[i][j+1]=='.'||str[i][j+1]=='-')&&(str[i+1][j]=='.'||str[i+1][j]=='|'))f[cur].insert(s|2|(1<<j*2),val);
				}else if(!r&&up){
					if(str[i+1][j]=='.'||str[i+1][j]=='|')f[cur].insert(s,val);
					if(str[i][j+1]=='.'||str[i][j+1]=='-')f[cur].insert(s^(up<<j*2)|up,val);
				}else if(r&&!up){
					if(str[i+1][j]=='.'||str[i+1][j]=='|')f[cur].insert(s^r|(r<<j*2),val);
					if(str[i][j+1]=='.'||str[i][j+1]=='-')f[cur].insert(s,val);
				}else if(r==1&&up==2){
					if(i==ex&&j==ey&&(s^r^(up<<j*2))==0)res+=val;
				}else if(r==2&&up==1){
					f[cur].insert(s^r^(up<<j*2),val);
				}else if(r==1&&up==1){
					int nxt=s^r^(up<<j*2);
					for(int u=j+1,p=1;u<=m;u++){
						int now=nxt>>u*2&3;
						p+=(now==1)-(now==2);
						if(!p){
							f[cur].insert(nxt^(3<<u*2),val);
							break;
						}
					}
				}else if(r==2&&up==2){
					int nxt=s^r^(up<<j*2);
					for(int u=j-1,p=1;u;u--){
						int now=nxt>>u*2&3;
						p+=(now==2)-(now==1);
						if(!p){
							f[cur].insert(nxt^(3<<u*2),val);
							break;
						}
					}
				}
			}
		}
	printf("%lld\n",res);
	return 0;
}


3.标识设计

不再是回路问题而是一个L型,发现L可以划分为两部分一部分是转角前的,另一部分是转角后的,但是仔细观察可以发现转角后的一定是横着的,所以可以0/1状压,但是我没想到所以还是三进制压的不过无所谓,注意一个插头本质上是钦定了两个格子必须选所以不需要特判拐角。



#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=32;
const int mod=2004013;
typedef long long ll;
struct HA{
	struct Edge{
		int nxt,s,cnt;
		ll val;
	}e[mod];
	int h[mod],idx;
	void insert(int s,int cnt,ll val){
		int x=s%mod;
		for(int i=h[x];i;i=e[i].nxt)
			if(e[i].s==s&&e[i].cnt==cnt){
				e[i].val+=val;
				return;
			}
		e[++idx].nxt=h[x];h[x]=idx;
		e[idx].cnt=cnt;e[idx].val=val;e[idx].s=s;
	}
	void clear(){
		for(int i=1;i<=idx;i++)h[e[i].s%mod]=0;
		idx=0;
	}
}f[2];
char str[N][N];
int main(){
	int n,m,ex,ey;
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++){
		scanf("%s",str[i]+1);
		for(int j=1;j<=m;j++)
			if(str[i][j]=='.')ex=i,ey=j;
	}
	ll res=0;
	int cur=1;
	f[0].insert(0,0,1);
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++,cur^=1){
			f[cur].clear();
			for(int k=1;k<=f[cur^1].idx;k++){
				int s=f[cur^1].e[k].s,cnt=f[cur^1].e[k].cnt;
				ll val=f[cur^1].e[k].val;
				int r=s&1,up=s>>j&1;
				if(i==ex&&j==ey){
					if(cnt==3){
						if(!up){
							if(s==1||s==0)res+=val;
						}
					}
				}else if(str[i][j]=='#'){
					if(!r&&!up)f[cur].insert(s,cnt,val);
				}else if(!r&&!up){
					f[cur].insert(s,cnt,val);
					if(cnt!=3&&str[i+1][j]=='.')f[cur].insert(s|1<<j,cnt+1,val);
				}else if(r&&!up){
					if(str[i][j+1]=='.')f[cur].insert(s,cnt,val);
					f[cur].insert(s^1,cnt,val);
				}else if(!r&&up){
					if(str[i+1][j]=='.')f[cur].insert(s,cnt,val);
					if(str[i][j+1]=='.')f[cur].insert(s^(1<<j)|1,cnt,val);
				}
			}
		}
	}
	printf("%lld\n",res);
	return 0;
}

4.神奇游乐园

找一条回路使得权值和最大。

类似的,用括号序列合并回路,可以从任意地方开始从任意地方结束,结束的标志是合并了一个左括号和一个右括号,此时统计答案即可。

#include<set>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=14;
int f[2][1<<N];
int a[110][110];
int main(){
	int n,m;
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			scanf("%d",&a[i][j]);
	int cur=1;
	memset(f,-0x3f,sizeof(f));
	const int Min=f[0][0];
	int lim=1<<2*(m+1);
	f[0][0]=0;
	int res=Min;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++,cur^=1){
			memset(f[cur],-0x3f,sizeof(f[cur]));
			for(int s=0;s<lim;s++){
				if(f[cur^1][s]==Min)continue;
				int r=s&3,up=s>>2*j&3;
				if(!r&&!up){
					f[cur][s]=max(f[cur][s],f[cur^1][s]);
					int nxt=s|2|(1<<j*2);
					if(i!=n&&j!=m)f[cur][nxt]=max(f[cur][nxt],f[cur^1][s]+a[i][j]);
				}else if(!r&&up){
					int nxt=s^(up<<j*2)|up;
					if(j!=m)f[cur][nxt]=max(f[cur][nxt],f[cur^1][s]+a[i][j]);
					if(i!=n)f[cur][s]=max(f[cur][s],f[cur^1][s]+a[i][j]);
				}else if(!up&&r){
					if(j!=m)f[cur][s]=max(f[cur][s],f[cur^1][s]+a[i][j]);
					int nxt=s^r|(r<<j*2);
					if(i!=n)f[cur][nxt]=max(f[cur][nxt],f[cur^1][s]+a[i][j]);
				}else if(up==2&&r==1){
					int nxt=s^r^(up<<j*2);
					if(!nxt)res=max(res,f[cur^1][s]+a[i][j]);
				}else if(up==1&&r==2){
					int nxt=s^r^(up<<j*2);
					f[cur][nxt]=max(f[cur][nxt],f[cur^1][s]+a[i][j]);
				}else if(up==1&&r==1){
					int nxt=s^r^(up<<j*2);
					for(int u=j+1,p=1;u<=m;u++){
						int now=nxt>>u*2&3;
						p+=(now==1)-(now==2);
						if(!p){
							nxt^=3<<u*2;
							f[cur][nxt]=max(f[cur][nxt],f[cur^1][s]+a[i][j]);
							break;
						}
					}
				}else if(up==2&&r==2){
					int nxt=s^r^(up<<j*2);
					for(int u=j-1,p=1;u;u--){
						int now=nxt>>u*2&3;
						p+=(now==2)-(now==1);
						if(!p){
							nxt^=3<<u*2;
							f[cur][nxt]=max(f[cur][nxt],f[cur^1][s]+a[i][j]);
							break;
						}
					}
				}
			}
		}
	}
	printf("%d\n",res);
}

5.地板

类似的L型,不过需要考虑倒过来的情况,所以只能三进制压缩。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=110;
const int p=20110520;
const int mod=2040103;
struct HA{
	struct Edge{
		int nxt,s,val;
	}e[mod];
	int h[mod],idx;
	void insert(int s,int val){
		int x=s%mod;
		for(int i=h[x];i;i=e[i].nxt){
			if(e[i].s==s){
				e[i].val+=val;
				if(e[i].val>=p)e[i].val-=p;
				return;
			}
		}
		e[++idx].s=s;e[idx].val=val;
		e[idx].nxt=h[x];h[x]=idx;
	}
	void clear(){
		for(int i=1;i<=idx;i++)
			h[e[i].s%mod]=0;
		idx=0;
	}
}f[2];
char str[N][N];
int main(){
	int n,m,ex,ey;
	scanf("%d%d",&n,&m);
	if(n<m){
		for(int i=1;i<=n;i++)
			for(int j=1;j<=m;j++){
				char ch=getchar();
				while(ch!='_'&&ch!='*')ch=getchar();
				str[j][i]=ch;
			}
		swap(n,m);
	}else for(int i=1;i<=n;i++)scanf("%s",str[i]+1);
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			if(str[i][j]=='_')ex=i,ey=j,str[i][j]='.';
	int cur=1;
	f[0].insert(0,1);
	int res=0;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++,cur^=1){
			f[cur].clear();
			for(int k=1;k<=f[cur^1].idx;k++){
				int s=f[cur^1].e[k].s,val=f[cur^1].e[k].val;
				int r=s&3,up=s>>j*2&3;
				if(i==ex&&j==ey){
					if(r==2&&up==0){
						if(s==2)res+=val;
					}else if(r==0&&up==2){
						if(s==(up<<j*2))res+=val;
					}else if(up==1&&r==1){
						if(s==(up<<j*2|1))res+=val;
					}
					if(res>=p)res-=p;
				}else if(str[i][j]=='*')f[cur].insert(s,val);
				else if(!r&&!up){
					bool pd1=(str[i][j+1]=='.'),pd2=(str[i+1][j]=='.');
					if(pd1)f[cur].insert(s|1,val);
					if(pd2)f[cur].insert(s|(1<<j*2),val);
					if(pd1&&pd2)f[cur].insert(s|2|(2<<j*2),val);
				}else if(!r&&up){
					if(up==1){
						if(str[i+1][j]=='.')f[cur].insert(s,val);
						if(str[i][j+1]=='.')f[cur].insert(s^(up<<j*2)|2,val);
					}
					if(up==2){
						f[cur].insert(s^(up<<j*2),val);
						if(str[i+1][j]=='.')f[cur].insert(s,val);
					}
				}else if(r&&!up){
					if(r==1){
						if(str[i][j+1]=='.')f[cur].insert(s,val);
						if(str[i+1][j]=='.')f[cur].insert(s^1|(2<<j*2),val);
					}
					if(r==2){
						f[cur].insert(s^2,val);
						if(str[i][j+1]=='.')f[cur].insert(s,val);
					}
				}else if(r==1&&up==1){
					f[cur].insert(s^1^(1<<j*2),val);
				}
			}
		}
	}
	printf("%d\n",res);
	return 0;
}


6.神秘的生物

联通块的问题,不能使用括号序列了,用最小表示法压缩状态即可。

posted @ 2020-12-18 06:46  An_Fly  阅读(105)  评论(1编辑  收藏  举报