zoukankan      html  css  js  c++  java
  • CF1569F Palindromic Hamiltonian Path

    一、题目

    点此看题

    有一个 (n) 个点 (m) 条边的无向图,字符集大小为 (k),问有多少种满足下列条件的在点上填字符的方案数:

    • 存在一条恰好经过每个点一次的路径,使得按经过顺序写下点上的字符,会得到一个回文串。

    (nleq 12,kleq 12)

    二、解法

    因为回文串的限制是若干对字符的相等关系,所以每个点具体是哪个字符是没有关系的,我们只需要知道哪些字符相等即可,最后用组合数算一下就行了,这就是贝尔数(集合划分计数),(n=12) 的贝尔数大概是 (10^5) 级别的。

    考虑怎么判断一个集合划分方案是合法的,可以考虑状压,从中线处往两边放置同色数对即可。

    暴力判断肯定是过不了的。考虑选数对相当于把集合拆分出来一个大小等于 (2) 的集合,可以把它看成转移的过程,那么初始状态就是所有集合的大小等于 (2) 的状态(这个可以硬搜出来),那么时间复杂度就是总状态数 (10^5) 乘以转移数了。

    三、总结

    对于小范围的题目,如果只有相等的限制可以考虑贝尔数来优化。

    整体 (dp) 是一个很重要的思想,如果原问题被拆分成了很多个关联性强的子问题可以考虑只用一次 (dp)

    #include <cstdio>
    #include <vector>
    #include <cstring>
    #include <iostream>
    #include <map>
    using namespace std;
    const int M = 12;
    #define int long long
    int read()
    {
    	int x=0,f=1;char c;
    	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
    	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
    	return x*f;
    }
    int n,m,k,ans,g[M][M],a[M],p[M],fac[M];map<int,bool> dp;
    void init(int nc)
    {
    	int x=n;
    	for(int i=0;i<n;i++)
    		if(p[i]==-1) {x=i;break;}
    	if(x==n)//reach the end state
    	{
    		int f[1<<6][M]={},p1[M]={},p2[M]={};
    		for(int i=0;i<n;i++)
    		{
    			p2[p[i]]=p1[p[i]];
    			p1[p[i]]=i;
    		}
    		for(int i=0;i<nc;i++)
    			if(g[p1[i]][p2[i]]) f[1<<i][i]=1;
    		for(int i=0;i<(1<<nc);i++)
    			for(int j=0;j<nc;j++) if(f[i][j])
    				for(int k=0;k<nc;k++)
    				{
    					if(i&(1<<k)) continue;
    					f[i|(1<<k)][k]|=(g[p1[k]][p1[j]] && g[p2[k]][p2[j]]);
    					f[i|(1<<k)][k]|=(g[p1[k]][p2[j]] && g[p2[k]][p1[j]]);
    				}
    		for(int i=0;i<nc;i++) if(f[(1<<nc)-1][i])
    		{
    			int num=0;
    			for(int j=0;j<n;j++) num=num*6+p[j];
    			dp[num]=1;break;
    		}
    		return ;
    	}
    	for(int i=x+1;i<n;i++) if(p[i]==-1)
    	{
    		p[x]=p[i]=nc;
    		init(nc+1);
    		p[x]=p[i]=-1;
    	}
    }
    int dfs(int p[])
    {
    	int rn[M],cur[M]={},t[M]={},cnt=0,num=0;
    	memset(rn,-1,sizeof rn);
    	for(int i=0;i<n;i++)
    		if(rn[p[i]]==-1) rn[p[i]]=cnt++;
    	for(int i=0;i<n;i++)
    	{
    		t[i]=rn[p[i]];cur[t[i]]++;
    		num=num*6+t[i];
    	}
    	if(dp.count(num)) return dp[num];
    	int res=0;
    	for(int i=0;i<n && !res;i++) if(cur[t[i]]>2)
    		for(int j=i+1;j<n;j++) if(t[i]==t[j])
    		{
    			int tmp=t[i];
    			t[i]=t[j]=cnt;
    			res|=dfs(t);
    			t[i]=t[j]=tmp;
    			if(res) break;
    		}
    	return dp[num]=res;
    }
    void zxy(int nc)
    {
    	int x=n;
    	for(int i=0;i<n;i++)
    		if(p[i]==-1) {x=i;break;}
    	if(x==n)
    	{
    		dfs(p);
    		return ;
    	}
    	for(int c=0;c<=nc;c++)
    		for(int i=x+1;i<n;i++) if(p[i]==-1)
    		{
    			p[x]=p[i]=c;
    			zxy(max(c+1,nc));
    			p[x]=p[i]=-1;
    		}
    }
    signed main()
    {
    	n=read();m=read();k=read();
    	for(int i=1;i<=m;i++)
    	{
    		int u=read()-1,v=read()-1;
    		g[u][v]=g[v][u]=1;
    	}
    	memset(p,-1,sizeof p);
    	init(0);
    	zxy(0);
    	fac[0]=1;
    	for(int i=1;i<=k;i++) fac[i]=fac[i-1]*i;
    	for(auto x:dp) if(x.second)
    	{
    		int num=x.first,mx=1;
    		while(num)
    		{
    			mx=max(mx,num%6+1);
    			num/=6;
    		}
    		if(mx<=k) ans+=fac[k]/fac[k-mx];
    	}
    	printf("%lld
    ",ans);
    }
    
  • 相关阅读:
    20200816
    20200815
    20200813
    20200811
    20200810
    20200806
    20200804
    20200803
    20200802
    20200801
  • 原文地址:https://www.cnblogs.com/C202044zxy/p/15309579.html
Copyright © 2011-2022 走看看