离谱的插头 DP
查看原帖
离谱的插头 DP
363036
chlchl楼主2023/1/9 17:38

这代码过了模板,写了快读,改了 __int128aa 数组全部赋 11 了,结果样例没过 + 8WA、2TLE……

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

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

void add(lll S, lll w){
	lll 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] = 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(lll i=1;i<=n;i++){
		for(lll j=1;j<=cnt[op];j++)
			sta[j][op] <<= 2;//每一次会新增两个插头,所以空出两位放
		for(lll j=1;j<=m;j++){
			memset(head, 0, sizeof(head));
			lst = op, op ^= 1;
			cnt[op] = 0;//这行和上面两行都是清空链表、滚动数组 
			for(lll k=1;k<=cnt[lst];k++){//枚举上一列的轮廓线状态 
				lll S = sta[k][lst], now = f[k][lst];
				//取出第 j 个和第 j + 1 个插头,/ 4^(j-1)%4 即可 
				lll b1 = (S >> ((j - 1) << 1)) % 4;//j-1是上插头(对应原来的第j+1个) 
				lll 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] * 2ll, 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){//删掉了两个左括号 
					lll k1 = 1;
					for(lll 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){
					lll k1 = 1;
					for(lll 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] * b2 - p[j - 1] * b1, 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(i == n && j == m)
					ans += now;
			}
		} 
	}
}

void write(lll x){
	if(x < 0)	putchar('-'), x = -x;
	if(x >= 10) write(x / 10);
	putchar(x % 10 + '0');
}

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