【洛谷P3349】[ZJOI2016]小星星
【洛谷传送门】
借鉴了 \(\mathtt{wind\_whisper}\) 的思路,他的【博客传送门】(不过我的代码写的比他好看多了)
题解
先不提及优化,一开始本人并没有设计出朴素 DP。(或许有些浮躁?)
设计 DP
看到数据范围还有题目里面玄学的对应关系,可以想到状压,用一维表示已被对应的节点状态。
考虑对于每一个转移的父子点对 \(u,v\),由于转移的条件就是有连边,所以 \(u,v\) 对应的点在原图中一定有连边,转移的时候把 \(u\) 当前子树和 \(v\) 子树的状态合并(也就是逻辑或),就可以顺利转移。
因此,设计 \(dp(u,stat,p)\) 表示 \(u\) 节点对应的节点是 \(p\),\(u\) 子树内对应状态为 \(stat\)。
优化 DP
可以用容斥优化,但是比较难。(先挖坑)
- 最简单的想法,基于 DP 本身的状态,每个子树对应的状态里 \(1\) 的个数一定等于子树大小,预处理出来可以省去大量枚举。
- 其次,枚举子树的时候类比树形背包,每次转移完 DP 之后再
siz[u]+=siz[v]
。 - 基本就这些,还可以有一点点无聊的优化,比如在 DP 值为 \(0\) 的时候直接跳出循环。
吸氧能够卡过,最大点约 \(850ms\)。
代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int INF = 0x3f3f3f3f,N = 20;
inline ll read()
{
ll ret=0;char ch=' ',c=getchar();
while(!(c>='0'&&c<='9')) ch=c,c=getchar();
while(c>='0'&&c<='9') ret=(ret<<1)+(ret<<3)+c-'0',c=getchar();
return ch=='-'?-ret:ret;
}
int n,m,head[N],ecnt=-1;
bool mp[N][N];
inline void init_edge(){memset(head,-1,sizeof(head)),ecnt=-1;}
struct edge
{
int nxt,to;
}a[N<<1];
inline void add_edge(int x,int y)
{
a[++ecnt]=(edge){head[x],y};
head[x]=ecnt;
}
ll dp[N][1<<18][N],siz[N];
int num[N][1<<18],cnt[N];
void dfs(int u,int fa)
{
siz[u]=1;
for(int i=1;i<=n;i++) dp[u][1<<(i-1)][i]=1LL;
//初始化,u对应任何一个点方案都为1
for(int i=head[u];~i;i=a[i].nxt)
{
int v=a[i].to;
if(v==fa) continue;
dfs(v,u);
for(int j=1;j<=cnt[siz[u]];j++)
for(int k=1;k<=cnt[siz[v]];k++)
{
int stat1=num[siz[u]][j];
int stat2=num[siz[v]][k];
if(stat1&stat2) continue;
for(int p=1;p<=n;p++)
if((1<<(p-1))&stat2)
{
if(!dp[v][stat2][p]) continue;
for(int q=1;q<=n;q++)
{
if(mp[q][p]&& ((1<<(q-1))&stat1) )
dp[u][stat1|stat2][q]+=dp[u][stat1][q]*dp[v][stat2][p];
}
}
}
siz[u]+=siz[v];
}
}
int main()
{
init_edge();
n=read(),m=read();
for(int i=1;i<=m;i++)
{
int u=read(),v=read();
mp[u][v]=mp[v][u]=1;
}
for(int i=1;i<n;i++)
{
int u=read(),v=read();
add_edge(u,v),add_edge(v,u);
}
for(int i=0;i<1<<n;i++)
{
int tcnt=0;
for(int j=1;j<=n;j++)
if((1<<(j-1))&i) tcnt++;
num[tcnt][++cnt[tcnt]]=i;
}
dfs(1,-1);
ll ans=0ll;
for(int i=1;i<=n;i++)
ans+=dp[1][(1<<n)-1][i];
printf("%lld\n",ans);
return 0;
}