插头 DP WA 60求助(带详细注释)!
查看原帖
插头 DP WA 60求助(带详细注释)!
363036
chlchl楼主2023/1/9 16:50

rt,不知道哪里的锅 qwq。

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

const int N = 12 + 4;
const int M = 600000 + 5;
const int hsh = 4007;
char s[N][N];
ll n, m, tx, ty, p[N], a[N][N];
ll ans, lst, op, f[M][2];
ll cnt[2], head[hsh + 10], nxt[M], sta[M][2];
//因为是滚动数组,cnt表示两个哈希表分别有多少个数
//sta存的是 % hsh + 1 = 1 ~ hsh 的所有轮廓线状态 

void add(ll S, ll w){
	ll u = S % hsh + 1;
	for(int i=head[u];i;i=nxt[i]){
		if(sta[i][op] == S){
			f[i][op] += w;
			return ;
		}
	}
	//% hsh + 1 中没有实际与它相同的轮廓线,我们就新建一个 
	nxt[++cnt[op]] = head[u];
	head[u] = head[cnt[op]];
	sta[cnt[op]][op] = S;
	f[cnt[op]][op] = w;
}

void dp(){
	cnt[op] = f[1][op] = 1;
	sta[1][op] = 0;
	for(ll i=1ll;i<=n;i++){
		for(ll j=1ll;j<=cnt[op];j++)
			sta[j][op] <<= 2ll;//每一次会新增两个插头,所以空出两位放
		for(ll j=1;j<=m;j++){
			memset(head, 0, sizeof(head));
			lst = op, op ^= 1;
			cnt[op] = 0;//这行和上面两行都是清空链表、滚动数组 
			for(ll k=1;k<=cnt[lst];k++){//枚举上一列的轮廓线状态 
				ll S = sta[k][lst], now = f[k][lst];
				//取出第 j 个和第 j + 1 个插头,/ 4^(j-1)%4 即可 
				ll b1 = (S >> ((j - 1) << 1)) % 4;//j-1是上插头(对应原来的第j+1个) 
				ll b2 = (S >> (j << 1)) % 4;//j 是左插头 
				if(!a[i][j]){//这里是障碍 
					if(!b1 && !b2)//当且仅当两个插头都为 0 时合法 
						add(S, now);
				}
				else if(!b1 && !b2){//左、上都被封死 
					if(a[i + 1][j] && a[i][j + 1])//当且仅当这俩插头都可以走的时候才合法 
						add(S + p[j - 1] + p[j] * 2, now);//新建一对匹配的括号 
				}
				else if(!b1 && b2){//上边封死左边没封,选一个走,下面情况同理 
					if(a[i][j + 1])//继承了之前的状态(方向没变插头编号自然也不变),不变 
						add(S, now);
					if(a[i + 1][j])//先把第 j 位变成 0,再把第 j-1 变成 b2
						add(S - p[j] * b2 + p[j - 1] * b2, now); 
				}
				else if(b1 && !b2){
					if(a[i][j + 1])//变化 
						add(S - p[j - 1] * b1 + p[j] * b1, now);
					if(a[i + 1][j])//继承 
						add(S, now);
				}
				else if(b1 == 1 && b2 == 1){//删掉了两个左括号 
					ll k1 = 1;
					for(ll l=j+1;l<=m;l++){//找后面第一个失配的右括号改成左括号 
						if((S >> (l << 1)) % 4 == 1)
							++k1;
						if((S >> (l << 1)) % 4 == 2)
							--k1;
						if(!k1){//前缀和为 -1,找到了多出的右括号 
							add(S - p[l] - p[j - 1] * b1 - p[j] * b2, now);
							//方便理(ctrl)解(c),b1、b2 都是 1,所以可以不写的
							//将 l 从 2 改到 1,就是减了一个 p[l] 而已 
							break;
						}
					}
				}
				else if(b1 == 2 && b2 == 2){
					ll k1 = 1;
					for(ll l=j-2;l>=0;l--){//找前面第一个失配的左括号改成右括号 
						if((S >> (l << 1)) % 4 == 1)
							--k1;
						if((S >> (l << 1)) % 4 == 2)
							++k1;
						if(!k1){//后缀和为 1,找到了多出的右括号 
							add(S + p[l] - p[j] * b1 - p[j - 1] * b2, now);
							//方便理解,b1、b2 都是 1,所以可以不写的 
							//将 l 从 1 改到 2,就是加了一个 p[l] 而已 
							break;
						}
					}
				}
				else if(b1 == 2 && b2 == 1)//反着的括号,直接去掉括号序列仍然匹配 
					add(S - p[j - 1] * b1 - p[j] * b2, now);
				else if(b1 == 1 && b2 == 2)//顺着的括号对,直接匹配 
					if(i == tx && j == ty)//已经是最后一个格子了,证明这是一条哈密尔顿回路 
						ans += now;
			}
		} 
	}
}

int main(){
	p[0] = 1;
	for(int i=1;i<N;i++)
		p[i] = p[i - 1] << 2;//预处理不怎么想解释 
	scanf("%lld%lld", &n, &m);
	for(int i=1;i<=n;i++){
		scanf("%s", s[i] + 1);
		for(int j=1;j<=m;j++)
			if(s[i][j] == '.')
				a[tx = i][ty = j] = 1;
	}
	dp();
	printf("%lld\n", ans);
	return 0;
} 
2023/1/9 16:50
加载中...