【洛谷】P8352 [SDOI/SXOI2022] 小 N 的独立集(dp套dp)

原题链接

题意

给定一棵 \(n\) 个点的树,每个点的权值在 \([1,k]\) 之间,对于 \(\forall i \in [1,kn]\),求出有多少种权值分配方案,使得树的最大权独立集大小为 \(i\)

\(n \leq 1000,k \leq 5\)

思路

不难想到一种很暴力的方式,即枚举每个点的权值,然后树形 dp 求出最大权独立集。即,设 \(f_{u,0/1}\) 表示在子树 \(u\) 中不选/选 \(u\) 时的最大权独立集。时间复杂度为 \(O(nk^n)\)

在本题中,注意到点权的范围实际上很小,一种理想的状态是令 \(g_{u,v}\)\(u\) 子树内最大权独立集大小为 \(v\) 的方案数。而在转移中,由于需要对当前点 \(u\) 是否被选取在独立集中分类讨论,那么可以进一步想到令 \(g_{u,v_0,v_1}\) 表示在 \(u\) 子树中不选择 \(u\) 节点时最大权独立集大小为 \(v_0\),选择 \(u\) 节点时最大权独立集大小为 \(v_1\) 的方案数。枚举当前儿子 \(v\) 的时候,令 \(p\)\(q\) 分别表示不选/选 \(v\) 节点的最大权独立集大小,可以得到状态转移方程:

\[g_{u,v0+\max(p,q),v1+p}=\sum_{v \in son_u} g_{u,v0,v1} \times g_{v,p,q} \]

不难发现这里的 \(v_0\)\(v_1\) 实质上就是之前提到的 \(f_{u,0}\)\(f_{u,1}\)。这种内层 dp 结果作为外层 dp 的状态的小 trick 就被称为 dp 套 dp。这样做的时间复杂度为 \(O(n^3k^4)\)。还需要考虑进一步优化。

注意到 \(f_{u,0}\)\(f_{u,1}\) 的差值实际上不会很大,考虑把这个差值的范围确定下来。可以重新定义 \(f_{v,0}\)\(f_{v,1}\) 的为是否强制不选\(u\) 节点时的最大权独立集大小,那么可以得到不等式 \(0 \leq f_{u,0}-f_{u,1} \leq a_u \leq k\)。于是就可以重新定义 \(g_{u,v_1,d}\) 表示 \(f_{u,0}=v_1+d,f_{u,1}=v_1\) 时的方案数。

由于状态重新定义,转移方程也发生了一定的变化。在枚举 \(f_{v,v_1,d}\) 时,同样令 \(p=v_1,q=d\),那么可以得到 \(v_1'=v_1+\max(p,p+q)=v_1+p+q\)\(v_0'=\max(v_1+p+q,v_1+d+p)\),于是就有 \(d'=v_0'-v_1'=\max(v_1+p+q,v_1+d+p)-v_1+p+q\),得到转移方程:

\[g_{u,v_1+p+q,d'}=\sum_{v \in son_u} g_{u,v1,d} \times g_{v,p,q} \]

此时的时间复杂度就降低到了 \(O(n^2k^4)\),可以通过本题。

code

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=1010,M=N*5,mod=1e9+7;
int tmp[M][6],f[N][M][6],siz[N],n,k,h[N],idx;
struct edge{int v,nex;}e[M];
void add(int u,int v){e[++idx]=edge{v,h[u]};h[u]=idx;}
void Add(int &a,int b){a+=b;if(a>=mod) a-=mod;}
void dfs(int u,int fa)
{
	siz[u]=1;
	for(int i=1;i<=k;i++) f[u][0][i]=1;
	for(int i=h[u];i;i=e[i].nex)
	{
		int v=e[i].v;if(v==fa) continue;dfs(v,u);memset(tmp,0,sizeof(tmp));
		for(int i=0;i<=k*siz[u];i++)
		    for(int j=0;j<=k;j++)
		    {
		    	if(!f[u][i][j]) continue;
		    	for(int p=0;p<=k*siz[v];p++)
		    	    for(int q=0;q<=k;q++)
		    	        Add(tmp[i+p+q][max(i+j+p,i+p+q)-(i+p+q)],1ll*f[u][i][j]*f[v][p][q]%mod);    
			}
		memcpy(f[u],tmp,sizeof(tmp));
		siz[u]+=siz[v];
	}
}
int main()
{
	scanf("%d%d",&n,&k);for(int u,v,i=1;i<n;i++) scanf("%d%d",&u,&v),add(u,v),add(v,u);dfs(1,0);
    for(int i=1;i<=k*n;i++)
	{
        int ans=0;
        for(int d=0;d<=min(i,k);d++) Add(ans,f[1][i-d][d]);
        printf("%d\n",ans);
    }
	return 0;
}
posted @ 2023-03-20 19:41  曙诚  阅读(42)  评论(0编辑  收藏  举报