https://ac.nowcoder.com/acm/contest/9328/G
觉得这题还挺好的,虽然2个半小时才过。。。。
这题巧妙在设计状态,如果直接dfs,我们发现有3种状态,一是已经被放了数字的点,二是可以放当前数字的点,三是需要放当前数字+1的点
那么就会变成3^n
但是我们知道最后要填满所有点,那么必须保证当前所有可以放当前数字的点都放完了以后,也就是状态二不存在了,我们才能开始考虑放下一个数字
也就是每个数字放完所有状态只剩下一三
所以我们就可以精简为两种状态,每次新增一些点使得点只剩一三状态
那么dp[s][i]表示第i个数字放完后,s这个点集已经被放完数字了,剩下的都是没放的
每次枚举要新增哪些点集的时候,设rs=s的补集,由于我们要把剩下的点都变成3状态,那么t必须被完全覆盖
也就是枚举rs的子集ns,通过预处理出来的ns能连出去的边覆盖的点集ks[ns],如果ks[ns]&rs==rs,说明只要把ns填满当前数字,就能把剩下的点全部变成三状态
预处理的时候还要判断ns是否内部是没有边的,否则ns这个点集就不行
虽然dp数组是n*2^n的,但是实际上只会有2^n的状态,因为每个s能放的数字实际上是固定的,由于要枚举子集,所以总复杂度是3^n的
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxl=20;
int n,m,mx;ll ans;
ll dp[1<<18][20];
int es[1<<18],ks[1<<18];
vector<int> e[maxl];
bool hse[20][20];
bool nono[1<<18];
inline ll dfs(int s,int now)
{
if(s==mx)
return 1ll;
if(dp[s][now]>=0)
return dp[s][now];
int z=0,rs=mx^s;
dp[s][now]=0;
for(int ns=rs;ns;ns=(ns-1)&rs)
if(!nono[ns] && (ks[ns]&rs)==rs)
dp[s][now]+=dfs(s|ns,now+1);
return dp[s][now];
}
int main()
{
//freopen("G.in","r",stdin);
scanf("%d%d",&n,&m);
mx=(1<<n)-1;
for(int i=1;i<=n;i++)
es[i]|=1<<(i-1);
for(int i=1;i<=m;i++)
{
int u,v;
scanf("%d%d",&u,&v);
e[u].push_back(v);
es[u]|=1<<(v-1);hse[u][v]=true;
e[v].push_back(u);
es[v]|=1<<(u-1);hse[v][u]=true;
}
for(int s=0;s<=mx;s++)
{
ks[s]=0;nono[s]=false;
for(int i=1;i<=n;i++)
if(s>>(i-1)&1)
{
for(int j=i+1;j<=n;j++)
if(s>>(j-1)&1)
nono[s]|=hse[i][j];
ks[s]|=es[i];
}
}
memset(dp,-1,sizeof(dp));
ans=dfs(0,0);
printf("%lld",ans);
return 0;
}