P8352-[SDOI/SXOI2022]小N的独立集【dp套dp】

正题

题目链接:https://www.luogu.com.cn/problem/P8352


题目大意

给出一棵树,每个点的权值是\([1,k]\)之间的一个数,对于\(i\in[1,nk]\)求令这棵树的最大独立集权值为\(i\)的方案数。

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


解题思路

考虑我们求最大独立集时的\(dp\)方程,设\(f_{i,0/1}\)表示\(i\)选/不选时的子树最大权值和。

注意到它总共有\(n^2k^2\)种状态,不能拿来dp套dp维护。

继续挖掘一下性质,若\(f_{i,0}\geq f_{i,1}\),那么\(f_{i,1}\)显然没有用,若\(f_{i,0}+k\leq f_{i,1}\)那么\(f_{i,0}\)也没有用,因为我们可以显然父节点不选择更优。

所以我们可以强制\(f_{i,1}\in[f_{i,0},f_{i,0}+k]\)这个范围,这样我们的状态数就只剩下\(nk^2\)种了。

\(g_{i,j,x}\)表示\(f_{i,0}=j,f_{i,1}=j+x\)的方案数,然后\(dp\)子树转移即可。

时间复杂度:\(O(n^2k^4)\)


code

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=1010,P=1e9+7;
struct node{
	int to,next;
}a[N<<1];
int n,k,tot,siz[N],ls[N],ans[N*5],f[N][N*5][6],g[N*5][6];
void addl(int x,int y){
	a[++tot].to=y;
	a[tot].next=ls[x];
	ls[x]=tot;return;
}
void Add(int &x)
{x=(x>=P)?(x-P):x;}
void dfs(int x,int fa){
	siz[x]=0;
	for(int i=1;i<=k;i++)f[x][0][i]=1;
	for(int i=ls[x];i;i=a[i].next){
		int y=a[i].to;
		if(y==fa)continue;dfs(y,x);
		for(int a=0;a<=siz[x];a++){
			for(int b=0;b<=k;b++){
				if(!f[x][a][b])continue;
				for(int c=0;c<=siz[y];c++)
					for(int d=0;d<=k;d++){
						int A=a+c+d,B=a+b+c;
						B=B-A;if(B<0)B=0;
						Add(g[A][B]+=1ll*f[x][a][b]*f[y][c][d]%P);
					}
			}
		}
		siz[x]+=siz[y]+k;
		for(int a=0;a<=siz[x];a++)
			for(int b=0;b<=k;b++)
				f[x][a][b]=g[a][b],g[a][b]=0;
	}
	return;
}
int main()
{
//	printf("%d\n",sizeof(f)>>20);
//	freopen("2.in","r",stdin);
//	freopen("2.out","w",stdout);
	scanf("%d%d",&n,&k);
	for(int i=1;i<n;i++){
		int x,y;
		scanf("%d%d",&x,&y);
		addl(x,y);addl(y,x);
	}
	dfs(1,0);
	for(int a=0;a<=siz[1];a++)
		for(int b=0;b<=k;b++)
			Add(ans[a+b]+=f[1][a][b]);
	for(int i=1;i<=n*k;i++)
		printf("%d\n",ans[i]);
	return 0;
}
posted @ 2022-08-06 15:09  QuantAsk  阅读(43)  评论(0编辑  收藏  举报