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!=5040\ll 2^{21}\) 种情况,并且与 bitmask 很多 match 不上,到时候筛掉。那怎么记录这玩意呢?可以用一个对 \(i\in[1,7]\),第 \(i\) 位奉 \(i\) 进一的数记录,第 \(i\) 位位权是 \((i-1)!\)。可以证明这样是压缩的最紧密的方式,因为 \(0\sim 7!\) 每个数都有对应。压缩和解压都简单,就跟普通的 \(x\) 进制数一样。

然后对转移的时候,不是要知道对 \(dp_{i,j,k}\) 的 \(j,k\) 以及新枚举的 \(o\) 对应的两两连通性吗,这可以并查集 \(\mathrm O(13\log13)\) 搞出来,然后它与 \(i\) 无关,可以预处理。复杂度理论上界 \(\mathrm O\!\left(4^n\times (n+1)!\times 4n^2+m\times 4^n\times (n+1)!\right)\)(\(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<<"!\n";
	}
	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]<<" \n"[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<<"!\n";
		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]<<"!\n";
		}
		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]<<"!\n";
	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]<<"!\n";
	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<<"\n";
	return 0;
}
上一篇:ubuntu命名


下一篇:atc工具模拟网络