zoukankan      html  css  js  c++  java
  • 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;
    }
    
    珍爱生命,远离抄袭!
  • 相关阅读:
    使用System.getProperty方法,如何配置JVM系统属性
    java的System.getProperty()方法可以获取的值
    文档系统
    会议系统
    MyEclipse优化】-----如何合理设置MyEclipse中的validation选项
    myeclipse10 java builder path libraries 添加tomcat
    WPF Step By Step 系列-Prism框架在项目中使用
    【MVVM Light】新手初识MVVM,你一看就会
    Android获取全部存储卡挂载路径
    VME总线
  • 原文地址:https://www.cnblogs.com/ycx-akioi/p/solution-atc-tdpc-grid.html
Copyright © 2011-2022 走看看