【洛谷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;
}
posted @ 2021-10-30 13:49  conprour  阅读(39)  评论(0编辑  收藏  举报