• ural1519-Formula 1


    题意

    给出一个 (n imes m) 的棋盘,上面有一些格子是不能经过的。求有多少种欧拉回路可以经过所有可经过到格子。(n,mle 12)

    分析

    上个月就看了一下插头dp,然而这道题写不出来。现在来看其实也非常好写,只要把情况讨论清楚,对插头dp理解好就可以了。

    我们要求的是欧拉回路,只能有一个环,所以要记录连通性状态,而不仅仅是像 hdu1693 这题一样,只记录轮廓线上是否有插头。

    (f(i,j,S)) 表示转移到 ((i,j)) 这个格子,轮廓线上方到格子全部被覆盖到,轮廓线状态为 (S) 的方案数,考虑如何表示状态 (S)

    观察轮廓线上方的连通性,可以发现,任意两条线都是不相交的,所以插头的匹配情况可以用一个合法的括号序列来表示。因此我们使用三进制表示法,0='',1='(',2=')' 。接下来讨论各种情况。

    下面的内容自己画一画图,讨论一下就可以得出了。

    将轮廓线从 1 到 (m+1) 标号,那么若当前为 ((i,j)) ,那么此处的下插头标号为 (j) ,这个值设为 (p) ,右插头标号为 (j+1) ,这个值设为 (q)((i,j-1)) 的左插头标号为 (j) ,这个值设为 (x),上插头标号为 (j+1) ,值设为 (y)

    • 此处是洞,那么只从 (x=y=0) 转移到 (p=q=0) 。否则:

    • (x=y=0) ,转移到 (p=1,q=2) ,新建一个连通分量

    • (x=y=1) ,转移到 (p=q=0)(y) 对应的右括号改为左括号

    • (x=y=2) ,转移到 (p=q=0)(x) 对应的左括号改为右括号

    • (x,y) 中只有一个为 0,转移到 (p=x+y,q=0)(p=0,q=x+y) ,延续之前到路线或者在此处转弯

    • (x=1,y=2) ,这是对当前的整个连通分量的末端合并,由于我们只能有一条回路,所以这个转移只能在棋盘的最后一个可走位置转移,(p=q=0)

    • (x=2,y=1) ,合并两个连通分量,(p=q=0)

    讨论就这样啦!

    写法上来说,用数组 (f,g) 滚动进行dp,用四进制数来存三进制状态,但这样直接开数组会爆炸,而且其中还有很多没有用的状态(不合法的括号序列),所以一开始我们dfs预处理一下状态,并给它们标号,用一个 unordered_map 来存状态到标号的对应。总的状态数上界大概是 (frac{inom {n} {frac{n}{2}}}{n+1}*2^frac{n}{2}*(n+1)) ,大约为 (1.1 imes 10^5) ,其实是一个比较松的上界。(卡特兰数为合法括号序列个数,删掉其中的某一些对,最后还有一个位置可以放空。其实还应该容斥空的位置,不过这是一个上界而已)。

    复杂度为 (O(nm|S|))

    代码

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long giant;
    const int maxn=14;
    const int maxg=1.2e5;
    unordered_map<int,int> id;
    int h[maxg],ids=0,a[maxn],mat[maxg][maxn],fx,fy,n,m;
    giant f[maxg],g[maxg];
    bool ok[maxn][maxn];
    inline void match(int mt[]) {
    	static int sta[maxn];
    	int top=0;
    	for (int i=1;i<=m+1;++i) if (a[i]==1) sta[++top]=i; else if (a[i]==2) {
    		int x=sta[top--];
    		mt[x]=i;
    		mt[i]=x;
    	}
    }
    inline bool legal() {
    	int tot=0;
    	for (int i=1;i<=m+1;++i) if ((tot+=(!a[i]?0:(a[i]==1?1:-1)))<0) return false;
    	if (tot) return false;
    	return true;
    }
    inline int at() {
    	int ret=0;
    	for (int i=m+1;i;--i) (ret+=a[i])<<=2;
    	return ret;
    }
    void dfs(int now) {
    	if (now>m+1) {
    		if (legal()) {
    			int x=at();
    			h[id[x]=++ids]=x; // some id is empty!! it equals zero
    			match(mat[ids]);
    		}
    		return;
    	}
    	for (int i=0;i<3;++i) a[now]=i,dfs(now+1);
    }
    inline int get(int x,int p) {
    	return (x>>(p<<1))&3;
    }
    inline int mod(int x,int p,int d) {
    	int tmp=d<<(p<<1),aux=3<<(p<<1);
    	return (x&(~aux))+tmp;
    }
    vector<int> dec(int x) {
    	vector<int> ret;
    	ret.clear();
    	for (int i=1;i<=m+1;++i) ret.push_back((x>>(i<<1))&3);
    	return ret;
    }
    int main() {
    #ifndef ONLINE_JUDGE
    	freopen("test.in","r",stdin);
    #endif
    	scanf("%d%d",&n,&m);
    	dfs(1);
    	for (int i=1;i<=n;++i) {
    		static char s[maxn];
    		scanf("%s",s+1);
    		for (int j=1;j<=m;++j) if (s[j]=='.') {
    			ok[i][j]=true;
    			fx=i,fy=j;
    		}
    	}
    	f[id[0]]=1;
    	for (int i=1;i<=n;++i) {
    		swap(f,g);
    		memset(f,0,sizeof f);
    		for (int j=1;j<=ids;++j) {
    			int d=h[j];
    			if (get(d,m+1)==0) f[id[d<<2]]=g[j];
    		}
    		for (int j=1;j<=m;++j) {
    			swap(f,g);
    			memset(f,0,sizeof f);
    			for (int k=1;k<=ids;++k) {
    				int d=h[k],x=get(d,j),y=get(d,j+1);
    				if (!ok[i][j]) {
    					if (!x && !y) f[k]+=g[k];
    				} else {
    					if (x==0 && y==0) {
    						int v=mod(mod(d,j,1),j+1,2);
    						int w=id[v];
    						f[w]+=g[k];
    					} else if (x==1 && y==1) {
    						int v=mod(mod(d,j,0),j+1,0);
    						v=mod(v,mat[k][j+1],1);
    						int w=id[v];
    						f[w]+=g[k];
    					} else if (x==2 && y==2) {
    						int v=mod(mod(d,j,0),j+1,0);
    						v=mod(v,mat[k][j],2);
    						int w=id[v];
    						f[w]+=g[k];
    					} else if (x==0 || y==0) {
    						int v1=mod(mod(d,j,x+y),j+1,0);
    						int v2=mod(mod(d,j,0),j+1,x+y);
    						int w1=id[v1],w2=id[v2];
    						f[w1]+=g[k],f[w2]+=g[k];
    					} else if (x==1 && y==2) {
    						if (i==fx && j==fy) {
    							int v=mod(mod(d,j,0),j+1,0);
    							int w=id[v];
    						 	f[w]+=g[k];
    						}
    					} else if (x==2 && y==1) {
    						int v=mod(mod(d,j,0),j+1,0);
    						int w=id[v];
    						f[w]+=g[k];
    					}
    				}
    			}
    		}
    	}
    	printf("%lld
    ",f[id[0]]);
    	return 0;
    }
    
  • 相关阅读:
    C
    A
    枚举子集的几种方法
    Codeforces Round #476 (Div. 2) [Thanks, Telegram!] ABCDE
    wannafly挑战赛14
    2018西安电子科大同步赛
    概率dp学习记录
    xcoj 1103 插线板(树链刨分求最大子段和)
    bzoj 2286(虚树+树形dp) 虚树模板
    bzoj3012(Trie)
  • 原文地址:https://www.cnblogs.com/owenyu/p/7476771.html
Copyright © 2020-2023  润新知