CF1569F Palindromic Hamiltonian Path

一、题目

点此看题

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

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

\(n\leq 12,k\leq 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\n",ans);
}
posted @ 2021-09-18 17:33  C202044zxy  阅读(250)  评论(0编辑  收藏  举报