习题:乃爱与城市拥挤程度(树形DP)

题目

传送门

思路

暴力做法大家都会,而且分还不低
考试的时候80分,以为卡一下常就过了
结果发现是数据出锅了,本来设计的暴力分为30
但是暴力其实与这道题的正解关系很大
当你打暴力的时候,你会发现很多问题被重复求解
DP自然而然的出现在代码之中

  • 子问题1
    怎么快速统计个数

\(dp[i][j]\)为以i号节点的子树距离小于k的城市数总和
dp的初始值为1,因为不管k为多少,都至少有它本身一个
\(dp[u][k]=1+\sum_{i}^{i\in son} dp[v][k-1]\)
注意到DP的定义,我们只会统计到子树,
我们并没有考虑到u号节点的父节点和祖先节点
但是通过u号节点的祖先节点可以间接的求出答案
每一个的贡献即\(dp[fa][z]-dp[son][z-1]\)
fa为son的唯一一个父亲,son的初值为u
综上,第一个子问题就解决了

  • 子问题2
    怎么快速统计拥挤度

这个时候,我们发现更新u号节点的时候,需要用到距离u号节点为j的总数
也就是说,我们对DP的定义的就需要发生改变
\(dp[i][j]\)为i号节点的距离为k的值
\(dp[u][j]=\sum_{i}^{i \in son}dp[i][j-1]\)
但是此刻选出的值不是真正的只考虑子树的值
同子问题1,可以通过父亲节点和祖先节点间接地求出
每一个的贡献\(dp[fa][z]\over dp[son][z-1]\)
son和fa的定义同子问题1

代码

#include<iostream>
#include<vector>
using namespace std;
const int mod=1e9+7;
int n,k;
int fat[100005];
int dp1[100005][15];
int dp2[100005][15];
int f1[100005];
int f2[100005];
vector<int> g[100005];
int qkpow(int a,int b)
{
	if(b==0)
		return 1;
	if(b==1)
		return a;
	int t=qkpow(a,b/2);
	t=(1ll*t*t)%mod;
	if(b%2==1)
		t=(1ll*a*t)%mod;
	return t;
}
void solve1(int u,int fa)
{
	fat[u]=fa;
	for(int i=0;i<=k;i++)
	{
		dp1[u][i]=dp2[u][i]=1;
	}
	for(int i=0;i<g[u].size();i++)
	{
		int v=g[u][i];
		if(v!=fa)
		{
			solve1(v,u);
			for(int j=1;j<=k;j++)
				{
					dp1[u][j]+=dp1[v][j-1];
				dp2[u][j]=((1ll*dp2[u][j]*dp2[v][j-1]%mod)*dp1[v][j-1])%mod;
			
			}
		}
	}
}
void solve2(int u,int fa)
{
	int now=u;
	int fa_num[15]={};
	f1[u]=fa_num[k]=dp1[u][k];
	int z=k;
	while(--z&&fat[now])
	{
		f1[u]=(f1[u]+dp1[fat[now]][z]-dp1[now][z-1]);
		fa_num[z]=f1[u];
		now=fat[now];
	}
	if(fat[now])
		f1[u]++;
	z=k;
	now=u;
	f2[u]=(1ll*dp2[u][k]*f1[u])%mod;
	while(--z&&fat[now])
	{
		f2[u]=((1ll*f2[u]*dp2[fat[now]][z]%mod)*qkpow(1ll*dp2[now][z-1]*dp1[now][z-1]%mod,mod-2)%mod)%mod;
		f2[u]=(1ll*f2[u]*(f1[u]-fa_num[z+1]))%mod;
		now=fat[now];
	}
	for(int i=0;i<g[u].size();i++)
	{
		int v=g[u][i];
		if(v!=fa)
			solve2(v,u);
	}
}
signed main()
{
	ios::sync_with_stdio(false);
	cin>>n>>k;
	for(int i=1;i<n;i++)
	{
		int s,e;
		cin>>s>>e;
		g[s].push_back(e);
		g[e].push_back(s);
	}
	solve1(1,0);
	solve2(1,0);
	for(int i=1;i<=n;i++)
		cout<<f1[i]<<' ';
	cout<<'\n';
	for(int i=1;i<=n;i++)
		cout<<f2[i]<<' ';
	return 0;
}

题外话

作为一个舰长,
我必须站出来,并大声喊出自己的理想
德丽莎世界第一可爱!!!

posted @ 2019-11-02 15:49  loney_s  阅读(203)  评论(0)    收藏  举报