[九省联考2018]秘密袭击coat

树形dp碾压标算

这道题目我写了60分的暴力(N^2*k),没有优化,只是说一说树形dp相关。

这道题我们可以转化一下,我们可以考虑每一个点对答案的贡献。这道题可以转化为以该点为根的树中包含根的连通块,且其中有k个点大于等于根的危险度的方案数。这里“等于”有一定的问题,就是同一种连通块可能会被其他的根重复统计,所以要特判一下,dan[s]==pan&&s<root 才认为其合法。
状态转移是O(k)的,具体看代码;

code:

#include<iostream>
#include<cstdio>
#include<queue>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
const int maxn=2006;
const int mod=64123;
vector<int>e[maxn];
int n,k,w,dan[maxn];
int val[maxn],size[maxn];
int f[maxn][maxn];
inline void dfs(int s,int fa,int pan,int root)
{
	if (dan[s]>pan||(dan[s]==pan&&s<root))
	{
		for (int i=1;i<k;++i)
		{
			f[s][i+1]=f[fa][i];
			if (f[s][i+1]>mod) f[s][i+1]-=mod;
		}
	}
	else for (int i=1;i<=k;++i) {f[s][i]=f[fa][i];if (f[s][i]>mod) f[s][i]-=mod;}
	for (int i=0;i<e[s].size();++i)
	{
		int v=e[s][i];
		if (v==fa) continue;
		dfs(v,s,pan,root);
	}
	for (int i=1;i<=k;++i) {f[fa][i]+=f[s][i];if (f[fa][i]>mod) f[fa][i]-=mod;}
}
signed main()
{
	cin>>n>>k>>w;
	for (int i=1;i<=n;++i)
	{
		scanf("%d",&dan[i]);
	}
	for (int i=1,a,b;i<=n-1;++i)
	{
		scanf("%d%d",&a,&b);
		e[a].push_back(b);
		e[b].push_back(a);
	}
	long long ans=0;
	for (int i=1;i<=n;++i)
	{
		memset(f,0,sizeof(f));
		f[i][1]=1;
		for (int j=0;j<e[i].size();++j)
		{
			dfs(e[i][j],i,dan[i],i);
		}
		ans=ans+f[i][k]%mod*dan[i]%mod;
		ans%=mod;
	} 
	cout<<ans;
	return 0;
}

收获:计数类树形依赖背包dp问题,可以先强制转移下来,这么一直转移到叶节点,最后回溯时加上儿子节点完善好的dp值。

原文地址:https://www.cnblogs.com/bullshit/p/9643096.html