AtC tdpc_grid

(n) 很小,(m) 很大,猜测是按列状压。

先考虑一个很拿衣服的 DP:(dp_{i,j,k})(i) 是当前列,(j) 是当前列的颜色 bitmask,(k) 是当前列与 ((1,1)) 的连通性 bitmask。然后转移的话,考虑刷表(肯定的嘛,填表真的要累死人),枚举下一列 bitmask (o),把 (j,k,o) 放一起并查集然后求出 (o) 对应的与 ((1,1)) 的连通性 bitmask。

如果规定横着只能往左走,那这个 DP 的正确性应该是易证的。关键这里的连通性是无向的,很容易找到一个该 DP 算法的反例:

111111000
000001000
000111000
000100000
000111111

这样可以发现第 4 列的时候,下面三行的元素明明是和 ((1,1)) 连通的,但是我们的转移算法却将它误判成了不连通。

DP 的转移出现了问题,思考为什么会出现这样的错误,以及如何补救。

很显然,是第 4 列右边的元素导致了 ((1,1))((3,4)) 连通,但是 DP 是从左往右考虑的,考虑到第 4 行的时候还没考虑到后面。那如果我们就是要考虑到后面的话,这玩意就有后效性了,不可取。考虑让步,我们其实在 DP 的时候并不需要知道在整张图中该列每个黑色元素与 ((1,1)) 是否连通,而只需要知道在前 (i) 列的子图中连不连通。这样最后的目标是最后一行的 DP 值,在最后一行处,原来的和新的 DP 定义等价(都是整张图)。

但是即使是改变了定义,DP 转移在第 4 行虽然不会出错了,在第 6 行又出错了……((5,6)) 明明是与 ((1,1)) 连通的,它却误判成了不连通。

思考为什么会出现这样的错误,以及如何补救。

是因为按原来的判断,认为 ((3,6))((1,1)) 连通后,忽视了 ((3,5))((5,5)) 的连通性,以为从 ((3,6)) 到达不了 ((5,6))。这样的算法处理不了「间接」与 ((1,1)) 连通的关系。考虑一下还有没有其它可能出现的错误:当前列中,如果黑色元素的上一列的同行元素没有一个是与 ((1,1)) 连通的话,那肯定是没救了,不管怎样都不会误判的;否则至少有一个位置「直接」被左边元素与 ((1,1)) 的连通性导致与 ((1,1)) 连通。其他可能也有些许「直接」连通的,但我们着眼看其中任意一个。那么对其他的与 ((1,1)) 连通的点,暂不考虑它是「直接」还是「间接」被导致连通,它一定都与我们着眼看的那个点连通(根据连通的传递性)。怎么个连通法呢?要么是只看这列,本身就在连续段里,这个情况旧算法本就可以处理;要么是先往左走,在去掉该列得到的子图中绕了一圈,然后往右走。这仅当它们俩左边的位置在阶段 (i-1) 的时候连通,充分性也不言而喻。于是对于上面「还有没有其它可能出现的错误」,回答是「Nope」。

那么现在只需要在 DP 状态里面再记录该列任意两个点(还额外包括 ((1,1)))的连通性,在转移的时候显然是能正确算出下一列的颜色 bitmask 对应的两两连通性的,刷表法累上去即可。

然后考虑怎么高效维护这东西。颜色 bitmask 就直接二进制状压呗。但是两两连通性怎么记录呢?给 (dbinom72=21) 对点两两记录 01?那承受不了。考虑把 7 个点从上往下排,记录每个点往下数多少第一个与之连通,如果没有就是 (0)。这样从下往上第 (i) 个点有 (i) 种可能,一共有 (7!=5040ll 2^{21}) 种情况,并且与 bitmask 很多 match 不上,到时候筛掉。那怎么记录这玩意呢?可以用一个对 (iin[1,7]),第 (i) 位奉 (i) 进一的数记录,第 (i) 位位权是 ((i-1)!)。可以证明这样是压缩的最紧密的方式,因为 (0sim 7!) 每个数都有对应。压缩和解压都简单,就跟普通的 (x) 进制数一样。

然后对转移的时候,不是要知道对 (dp_{i,j,k})(j,k) 以及新枚举的 (o) 对应的两两连通性吗,这可以并查集 (mathrm O(13log13)) 搞出来,然后它与 (i) 无关,可以预处理。复杂度理论上界 (mathrm O!left(4^n imes (n+1)! imes 4n^2+m imes 4^n imes (n+1)! ight))(4n^2) 是构造新两两连通性的时候),大概是 2e9,然后 TL = 8s,我却只跑了 150ms,可能是筛掉的不合法状态比较多吧。

code
#include<bits/stdc++.h>
using namespace std;
#define pb push_back
const int N=20,M=110;
const int mod=1000000007;
int n,m;
int fact[N];
int dp[M][1<<6][5040];
bool ok[1<<6][5040];
int to[1<<6][5040][1<<6];
int cont[5040];
struct ufset{
	int fa[N];
	void init(){memset(fa,0,sizeof(fa));}
	int root(int x){return fa[x]?fa[x]=root(fa[x]):x;}
	void mrg(int x,int y){
		x=root(x),y=root(y);
		if(x!=y)fa[x]=y;
	}
}ufs;
int main(){
	cin>>n>>m;
	fact[0]=1;for(int i=1;i<=n+1;i++)fact[i]=fact[i-1]*i;
	for(int i=0;i<1<<n;i++)if(i&1){
		int id=fact[n];
		for(int j=1;j<n;j++)if(i&1<<j-1&&i&1<<j)id+=fact[n-j];
		dp[1][i][id]=1;
//		cout<<i<<" "<<id<<"!
";
	}
	for(int j=0;j<1<<n;j++)for(int k=0;k<fact[n+1];k++){
		vector<int> v;int k0=k;
		for(int o=1;o<=n;o++)v.pb(k0%o),k0/=o;
		v.pb(k0);
//		cout<<k<<":";for(int o=0;o<v.size();o++)cout<<v[o]<<" 
"[o+1==v.size()];
		bool flg=true;
		for(int o=1;o<n;o++)if(j&1<<o-1&&j&1<<o&&v[n-o]!=1)flg=false;
		for(int o=1;o<=n;o++)if(!(j&1<<o-1)&&v[n-o])flg=false;
		for(int o=0;o<=n;o++)if(v[n-o]&&!(j&1<<o+v[n-o]-1))flg=false;
		ok[j][k]=flg;
		if(!flg)continue;
//		k0=0;for(int o=0;o<=n;o++)k0+=v[n-o]*fact[n-o];assert(k0==k);
//		cout<<j<<" "<<k<<"!
";
		for(int o=0;o<1<<n;o++){
			int &id=to[j][k][o];
			ufs.init();
			for(int p=0;p<=n;p++)ufs.mrg(p+1,p+1+v[n-p]);
			for(int p=1;p<=n;p++)if(j&1<<p-1&&o&1<<p-1)ufs.mrg(p+1,p+n+1);
			for(int p=1;p<n;p++)if(o&1<<p-1&&o&1<<p)ufs.mrg(p+n+1,p+n+2);
			for(int p=n+1;p<=2*n+1;p++){
				for(int q=p+1;q<=2*n+1;q++)if(ufs.root(p==n+1?1:p)==ufs.root(q)){
					assert(p!=n+1||o);
					id+=(q-p)*fact[n-(p-n-1)];break;
				}
			}
//			cout<<j<<" "<<k<<" "<<o<<":"<<to[j][k][o]<<"!
";
		}
		ufs.init();
		for(int p=0;p<=n;p++)ufs.mrg(p+1,p+1+v[n-p]);
		cont[k]=ufs.root(1)==ufs.root(n+1);
	}
	for(int j=0;j<1<<n;j++)for(int k=0;k<fact[n+1];k++)if(ok[j][k])for(int o=0;o<1<<n;o++)assert(ok[o][to[j][k][o]]);
//	cout<<cont[3][3]<<"!
";
	for(int i=1;i<m;i++)for(int j=0;j<1<<n;j++)for(int k=0;k<fact[n+1];k++)if(ok[j][k]){
		for(int o=0;o<1<<n;o++){
			int &d=dp[i+1][o][to[j][k][o]];
//			if(o==3&&to[j][k][o]==3)puts("yes!");
			d+=dp[i][j][k];d>=mod&&(d-=mod);
		}
	}
//	cout<<to[3][3][2]<<"!
";
	int ans=0;
	for(int j=0;j<1<<n;j++)for(int k=0;k<fact[n+1];k++)if(cont[k]){
		(ans+=dp[m][j][k])%=mod;
	}
	cout<<ans<<"
";
	return 0;
}
珍爱生命,远离抄袭!
原文地址:https://www.cnblogs.com/ycx-akioi/p/solution-atc-tdpc-grid.html