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);
}