【学习小结】插头DP

论文部分

orz litble

基于连通性的状态压缩方法

1. 最小表示法压缩联通块

可以把联通块从左到右最小标号。也可以按照同一联通块中的最小下标标号

2. 对于路径用括号序表示

因为路径不可能相交,所以这样表示可以大大减少状态
如果不是回路,而是路径,记录独立插头,但独立插头最多只有两个

3. 只对插头状压连通性

其他部分因为已经联通,不用考虑

4. 最后用hash来存状态

直接在插入状态的时候更新DP值。
用位运算简化代码和加速。
不用写繁琐的encode和extract函数

void ins(int zt,ll num){ //直接在hash的过程中更新答案
	int tmp = zt % Has + 1;
	for (int i = h[tmp] ; i ; i = ne[i])
		if ( a[now][i] == zt ) { f[now][i] += num; return; }
	ne[++tot[now]] = h[tmp] , h[tmp] = tot[now]; //在链表前面添加
	a[now][tot[now]] = zt , f[now][tot[now]] = num;
}

例题

注意:
转移一定要推清楚!不能有半点马虎!
插头DP的难点在于不知道该怎么调。至少无法输出调试。只能肉眼查错
必须尽量一次AC

1. Ural 1519 Formula 1

论文题
括号序表示插头连通性

如果可以用多个回路覆盖?
那么不需要记录连通性,直接用插头转移

一个细节错误:找括号匹配的时候忽略了没有插头的情况,调了好久,1个多小时!

#include<bits/stdc++.h>
using namespace std;

#define rep(i,l,r) for(register int i = l ; i <= r ; i++)
#define repd(i,r,l) for(register int i = r ; i >= l ; i--)
#define rvc(i,S) for(register int i = 0 ; i < (int)S.size() ; i++)
#define rvcd(i,S) for(register int i = ((int)S.size()) - 1 ; i >= 0 ; i--)
#define fore(i,x)for (register int i = head[x] ; i ; i = e[i].next)
#define forup(i,l,r) for (register int i = l ; i <= r ; i += lowbit(i))
#define fordown(i,id) for (register int i = id ; i ; i -= lowbit(i))
#define pb push_back
#define prev prev_
#define stack stack_
#define mp make_pair
#define fi first
#define se second
#define lowbit(x) ((x)&(-(x)))

typedef long long ll;
typedef long double ld;
typedef int ull;
typedef pair<int,int> pr;

const ld inf = 2e18;
const int N = 3e6 + 10;
const int maxn = 1000020;
const ll mod = 1e9 + 7;
const int LIM=300005,Has=299989;

int h[300005],a[2][LIM],ne[LIM],now,las,bin[30],mul[30],ex,ey,tot[2];
ll f[2][LIM],ans;
char ch[20][20];
int n,m;

void init(){
	bin[1] = 1;
	for (int i = 2 ; i <= 13 ; i++) bin[i] = bin[i - 1] * 4;
	for (int i = 1 ; i <= 13 ; i++) mul[i] = i * 2;
	for (int i = 1 ; i <= n ; i++){
		for (int j = 1 ; j <= m ; j++){
			if ( ch[i][j] == '.' ){
				ex = i , ey = j;
			}
		}
	}
}
void ins(int zt,ll num){ //直接在hash的过程中更新答案
	int tmp = zt % Has + 1;
	for (int i = h[tmp] ; i ; i = ne[i])
		if ( a[now][i] == zt ) { f[now][i] += num; return; }
	ne[++tot[now]] = h[tmp] , h[tmp] = tot[now]; //在链表前面添加
	a[now][tot[now]] = zt , f[now][tot[now]] = num;
}
inline int getS(int v,int id){
	return (v >> mul[id - 1]) & 3;
}
void clear(int t){
	memset(h,0,sizeof(h)) , memset(ne,0,sizeof(ne));
	rep(i,0,tot[t]) a[t][i] = 0 ,f[t][i] = 0;
	tot[t] = 0;
}
void solve(){
	ins(0,1);
	rep(i,1,n){
		rep(j,1,m){
			las = now , now ^= 1;
			clear(now);
			//cout<<"ans "<<i<<" "<<j<<" num "<<endl;
			rep(k,1,tot[las]){
				int v = a[las][k]; ll num = f[las][k];
				if ( !num ) continue;
				if ( j == 1 ){
					if ( getS(v,m + 1) ) continue;
					v <<= 2;
				}
			//	cout<<"from S \n";
			//rep(l,1,m + 1) cout<<getS(v,l)<<" ";
			//cout<<endl;
			//	cout<<" "<<k<<" "<<v<<" "<<num<<endl;
			//	cout<<" "<<k<<" "<<v<<" "<<num<<endl;
				int b1 = getS(v,j) , b2 = getS(v,j + 1) , tag = ch[i][j] == '.';//b1 : 左插头状态 , b2 : 下插头状态
				int tmpv = v - b1 * bin[j] - b2 * bin[j + 1];
				
				if ( !tag ){
					if ( !b1 && !b2 ) ins(v,num);
					continue;
				}
				if ( !b1 && !b2 ){
					ins(tmpv + bin[j] + bin[j + 1] * 2,num);
				}
				else if ( !b1 && b2 ){
					ins(v,num);
				//	ins(tmpv + bin[j],num);
					ins(tmpv + bin[j] * b2,num);
				}
				else if ( b1 && !b2 ){
					ins(v,num);
					ins(tmpv + bin[j + 1] * b1,num);
				}
				else if ( b1 == b2 ){ //括号类型相同,合并当前括号,并找到匹配的括号,翻转
					if ( b1 == 1 ){
						int id = 0 , sum = 1;
						rep(l,j + 2,m + 1){
							int c = getS(v,l);
							if ( c == 2 ) sum--;
							else if ( c == 1 ) sum++; //注意没有插头的情况!
							if ( !sum ){
								id = l; break; 
							}				
						}
						assert(id);
						ins(tmpv - bin[id],num); //右括号变成左括号
					}
					else{
						int id = 0 , sum = 1;
						repd(l,j - 1,1){
							int c = getS(v,l);
							if ( c == 1 ) sum--;
							else if ( c == 2 ) sum++;
							if ( !sum ){
								id = l; break;
							}
						}
						assert(id);
						ins(tmpv + bin[id],num);
					}
				}
				else if ( b1 != b2 ){
					if ( b1 == 2 ) ins(tmpv,num); //直接合并括号
					else if ( !tmpv && b1 == 1 && i == ex && j == ey ){
					 //	cout<<num<<endl;
					 	ans += num; //最后一个位置更新答案,不能有其他插头
					 }
				}
			}
		}
	}
	cout<<ans<<endl;
}
int main(){
	freopen("input.txt","r",stdin);

	scanf("%d %d",&n,&m);
	rep(i,1,n) scanf("%s",ch[i] + 1);
	init();
	solve();
}
2. 七月集训 by HbFs
3. GDKOI2016 Map 地图

题目大意:有一张n×m n \times mn×m的地图,有些地方是平地’.’,有些是障碍’#’,有些是未知的’?’,还有入口’S’,出口’E’和神器’X’。
合法的地图只有一个入口一个出口一个神器,并且入口,出口和神器两两连通。
现在要确定未知格子’?'是什么,问有多少种确定未知格子的方案使得地图合法?保证至少存在一种方案。
1 ≤ n ≤ 7 , 1 ≤ m ≤ 7 1 \leq n \leq 7,1 \leq m \leq7 1n71m7

这道题只要求联通,就需要最小表示法记录路径。可以看前面litble的博客

4. Black & White

这题主要是优化状态

后面的题都还没有写,一定要抽时间写,才能掌握!

5. 2331: [SCOI2011]地板

题意:用L形方块铺地板

题解:
插头记录一下是否需要拐弯

注意保留插头,但是标号会改变!
还有转移的时候插头种类也要保留

#include<bits/stdc++.h>
using namespace std;

#define rep(i,l,r) for(register int i = l ; i <= r ; i++)
#define repd(i,r,l) for(register int i = r ; i >= l ; i--)
#define rvc(i,S) for(register int i = 0 ; i < (int)S.size() ; i++)
#define rvcd(i,S) for(register int i = ((int)S.size()) - 1 ; i >= 0 ; i--)
#define fore(i,x)for (register int i = head[x] ; i ; i = e[i].next)
#define forup(i,l,r) for (register int i = l ; i <= r ; i += lowbit(i))
#define fordown(i,id) for (register int i = id ; i ; i -= lowbit(i))
#define pb push_back
#define prev prev_
#define stack stack_
#define mp make_pair
#define fi first
#define se second
#define lowbit(x) ((x)&(-(x)))

typedef long long ll;
typedef long double ld;
typedef int ull;
typedef pair<int,int> pr;

const ld inf = 2e18;
const int N = 3e6 + 10;
const int maxn = 1000020;
const ll mod = 20110520;
const int LIM=300005,Has=299989;

int h[300005],a[2][LIM],ne[LIM],now,las,bin[30],mul[30],ex,ey,tot[2];
ll f[2][LIM],ans;
char ch[120][120],ch2[120][120];
int n,m;

void init(){
	bin[1] = 1;
	for (int i = 2 ; i <= 13 ; i++) bin[i] = bin[i - 1] * 4;
	for (int i = 1 ; i <= 13 ; i++) mul[i] = i * 2;
	if ( n < m ){
		swap(n,m);
		rep(i,1,n) rep(j,1,m) ch2[i][j] = ch[j][i];
		memcpy(ch,ch2,sizeof(ch));
	}
	for (int i = 1 ; i <= n ; i++){
		for (int j = 1 ; j <= m ; j++){
			if ( ch[i][j] == '_' ){
				ex = i , ey = j;
			}
		}
	}
}
inline void up(ll &x,ll y){ x = (x + y) % mod; }
inline int getS(int v,int id){
	return (v >> mul[id - 1]) & 3;
}
void ins(int zt,ll num){ //直接在hash的过程中更新答案
	//cout<<"cur S \n";
	//rep(i,1,m + 1) cout<<getS(zt,i)<<" ";
	//cout<<endl;
	int tmp = zt % Has + 1;
	for (int i = h[tmp] ; i ; i = ne[i])
		if ( a[now][i] == zt ) { up(f[now][i],num); return; }
	ne[++tot[now]] = h[tmp] , h[tmp] = tot[now]; //在链表前面添加
	a[now][tot[now]] = zt , f[now][tot[now]] = num;
}

void clear(int t){
	memset(h,0,sizeof(h)) , memset(ne,0,sizeof(ne));
	rep(i,0,tot[t]) a[t][i] = 0 ,f[t][i] = 0;
	tot[t] = 0;
}
void solve(){
	ins(0,1);
	rep(i,1,n){
		rep(j,1,m){
			las = now , now ^= 1;
			clear(now);
		//	cout<<"ans "<<i<<" "<<j<<endl;
			rep(k,1,tot[las]){
				int v = a[las][k]; ll num = f[las][k];
				if ( !num ) continue;
				if ( j == 1 ){
					if ( getS(v,m + 1) ) continue;
					v <<= 2;
				}
		//		cout<<"from S \n";
	//rep(l,1,m + 1) cout<<getS(v,l2 2)<<" ";
	//cout<<endl;
			//	cout<<" "<<k<<" "<<v<<" "<<num<<endl;
				int b1 = getS(v,j) , b2 = getS(v,j + 1) , tag = ch[i][j] == '_';//b1 : 左插头状态 , b2 : 下插头状态
				int tmpv = v - b1 * bin[j] - b2 * bin[j + 1];
				
				if ( !tag ){
					if ( !b1 && !b2 ) ins(v,num);
					continue;
				}
				if ( !b1 && !b2 ){
					ins(tmpv + bin[j] + bin[j + 1],num);
					ins(tmpv + bin[j + 1] * 2,num);
					ins(tmpv + bin[j] * 2,num);
				}
				else if ( !b1 && b2 ){
					//ins(v,num);
					ins(tmpv + bin[j] * b2,num); //插头保留但是标号变化!
					//关闭插头或者拐弯
					if ( b2 == 1 ) ins(tmpv,num);
					else ins(tmpv + bin[j + 1],num);
				}
				else if ( b1 && !b2 ){
					//ins(v,num);
					ins(tmpv + bin[j + 1] * b1,num); //插头保留但是标号变化!
					if ( b1 == 1 ) ins(tmpv,num);
					else ins(tmpv + bin[j],num);
				}
				else if ( b1 == b2 && b2 == 2 ){
					ins(tmpv,num);
				}
			}	
		}
	}
	rep(k,1,tot[now]) if ( !a[now][k] ) up(ans,f[now][k]);
	cout<<ans<<endl;
}
int main(){
	//freopen("input.txt","r",stdin);

	scanf("%d %d",&n,&m);
	rep(i,1,n) scanf("%s",ch[i] + 1);
	init();
	solve();
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值