题目F:给你一个无向简单图,该图有n个节点,n为偶数,m条边。有k个不同的字母,你可以给每个节点标记一个字母。对于一种标记序列,如果存在一条哈密顿通路,使得该通路经过的节点的标记构成一个回文字符串,则该序列为完美序列。求有多少种完美序列。
题解:首先将n个节点两两一组进行划分,分成n/2组。每一组的两个数对应回文串中关于中点对称的两个位置,两个位置上对应的字母相同。不同组对应的字母不同。划分可以采用深度优先搜索遍历,每次第一个选取没有被分组的编号最小的节点和后面没有被分组的任意节点组成一组,这样可以做到不重不漏。当一种划分划分完成之后,对这些划分进行拼接,看能否将这些划分拼成一个哈密顿通路,并且每一组的两个节点与该路径中点的距离相等。首先寻找内部连接的一组,然后再选一组放在该组外侧,直到全部放置完毕。
void brute(int n,vector<int> &p) {
int x=find(p.begin(),p.end(),-1)-p.begin();
if(x==int(p.size())) {//此处代码省略}
for(int y=x+1; y<int(p.size()); ++y)if(p[y]==-1) {
p[x]=p[y]=n;
brute(n+1,p);
p[x]=p[y]=-1;
}
}
如该图,如果划分为[1,2]、[3,4],[1,2]内部连接,此时将[3,4]放到[1,2]外侧,有两种可能[3,1,2,4]和[4,1,2,3]都不能构成哈密顿通路,[3,4]同理。
如果划分为[1,3]、[2,4],则 [1,2,4,3]可以构成哈密顿通路。[3,2,4,1]不可以构成哈密顿通路,[1,3]内部不相连。
定义dp2[state][j]表示到达i状态,最后添加的是第j组后该部分是否是哈密顿通路。其中state的第k位如果是0表示第k组还没加入,如果 是1,表示已经加入。
forn(i,n)if(g[pos1[i]][pos2[i]])
dp2[1<<i][i]=true;//pos1[i]表示第i组第一个的点的编号,pos2[i]为第二个的编号
forn(mask,1<<n)forn(i,n)if(dp2[mask][i]) {
forn(j,n)if(!((mask>>j)&1)) {
dp2[mask|(1<<j)][j]|=(g[pos1[i]][pos1[j]]&&g[pos2[i]][pos2[j]]);
dp2[mask|(1<<j)][j]|=(g[pos1[i]][pos2[j]]&&g[pos2[i]][pos1[j]]);
}
}
如果最终某个i使得 dp2[(1<<n)-1][i]为true,则说明当前划分可以拼接成一个哈密顿通路。用一个六进制数表示当前划分,存在图中。因为最多存在六种不同的字母,所以是六进制。
forn(i,n)if(dp2[(1<<n)-1][i]) {
long long num=0;
for(int x:p)num=num*6+x;
dp[num]=true;
break;
}
这样不同组颜色不同的方案都被找出来了,然而可能不同的组颜色是相同的,接下来找到所有满足的字母划分方案。划分方案2与划分方案1基本相同,只是可能存在不同的组标记了相同字母。 此时如果将标记字母相同的多个组根据第一种划分修改改为不同的标记字母,那么将对应着划分方案1时的一种情况。如果对应的情况可行,则该情况可行。并将map中该位置设为true。
对于map中每一个为true的数字,将他不断模6并且除6,该过程中每一次模6后得到的最大值设为mx,并将mx+1。mx表示的是当前分组情况下,将改图分成了几个组,每一组组内的颜色相同,组间颜色不同。如果mx<k,则。即先从k种颜色中选出mx种颜色,然后每一组选自己对应的颜色。
#include<bits/stdc++.h>
#define forn(i,n)for(int i=0;i<int(n);i++)
using namespace std;
vector<vector<char> > g;
map<long long,bool> dp;
void brute(int n,vector<int> &p) {//第一中划分
int x=find(p.begin(),p.end(),-1)-p.begin();
if(x==int(p.size())) {//划分完成
vector<vector<char>> dp2(1<<n,vector<char>(n));
vector<int> pos1(n),pos2(n);
forn(i,p.size()) {
pos1[p[i]]=pos2[p[i]];
pos2[p[i]]=i;
}
//拼接
forn(i,n)if(g[pos1[i]][pos2[i]])dp2[1<<i][i]=true;
forn(mask,1<<n)forn(i,n)if(dp2[mask][i]) {
forn(j,n)if(!((mask>>j)&1)) {
dp2[mask|(1<<j)][j]|=(g[pos1[i]][pos1[j]]&&g[pos2[i]][pos2[j]]);
dp2[mask|(1<<j)][j]|=(g[pos1[i]][pos2[j]]&&g[pos2[i]][pos1[j]]);
}
}
//判断是否可行
forn(i,n)if(dp2[(1<<n)-1][i]) {
long long num=0;
for(int x:p)num=num*6+x;
dp[num]=true;
break;
}
return;
}
for(int y=x+1; y<int(p.size()); ++y)if(p[y]==-1) {//划分
p[x]=p[y]=n;
brute(n+1,p);
p[x]=p[y]=-1;
}
}
bool dfs(vector<int> p) {//判断划分是否可行
vector<int> used(int(p.size()),-1);
int cnt=0;
forn(i,p.size())if(used[p[i]]==-1)
used[p[i]]=cnt++;
long long num=0;
for(int& x:p) {
x=used[x];
num=num*6+x;
}
if(dp.count(num))return dp[num];
bool res=false;
vector<int> cur(cnt);
forn(i,p.size())++cur[p[i]];
forn(i,p.size())if(cur[p[i]]>2) {
int x=p[i];
for(int j=i+1; j<int(p.size()); ++j)if(p[j]==p[i]) {
p[i]=p[j]=cnt;
if(dfs(p)) {
res=true;
break;
}
p[i]=p[j]=x;
}
break;
}
return dp[num]=res;
}
void brute2(int n,vector<int> &p) {//第二种划分
int x=find(p.begin(),p.end(),-1)-p.begin();
if(x==int(p.size())) {//划分完成
dfs(p);
return;
}
forn(i,n+1) {//划分
for(int y=x+1; y<int(p.size()); ++y)if(p[y]==-1) {
p[x]=p[y]=i;
brute2(max(n,i+1),p);
p[x]=p[y]=-1;
}
}
}
int main() {
int n,m,k;
scanf("%d%d%d",&n,&m,&k);
g.resize(n,vector<char>(n));
forn(_,m) {
int v,u;
scanf("%d%d",&v,&u);
--v,--u;
g[v][u]=g[u][v]=1;
}
vector<int> cur(n,-1);
brute(0,cur);
brute2(0,cur);
vector<long long> fact(k+1);
fact[0]=1;
for(int i=1; i<=k; ++i)fact[i]=fact[i-1]*i;
long long ans=0;
for(auto it:dp)if(it.second) {
long long num=it.first;
long long mx=1;
while(num) {
mx=max(mx,num%6+1);
num/=6;
}
if(mx<=k) {
ans+=fact[k]/fact[k-mx];
}
}
printf("%lld\n",ans);
return 0;
}