树形DP水题系列(1):FAR-FarmCraft [POI2014][luogu P3574]

时间:2023-03-09 19:41:48
树形DP水题系列(1):FAR-FarmCraft [POI2014][luogu P3574]

题目

大意:

边权为1 使遍历树时到每个节点的时间加上点权的最大值最小 求这个最小的最大值

思路:

最优化问题 一眼树形DP

考虑状态设立 先直接以答案为状态 dp[u] 为遍历完以u为根的子树的答案

再考虑状态转移 dp[u]=MAX(dp[to]+1,siz+dp[to]);siz为枚举子树到以to为节点的子树时之前已遍历的总时间

很明显这个转移过来的dp值的最优化依赖于子树遍历的顺序 所以我们需要找到一种最优的子树遍历顺序来使每个子树得到最优的dp值来更新

我们通过观察可以发现 交换任意两个相邻的子树在遍历时的顺序不会对它们之前的子树和之后的子树造成影响 所以我们考虑贪心 如果只有两个子树 a和b 遍历完子树a,b的时间分别为 siz[a],siz[b],dp值分别为dp[a],dp[b];

将 a优先遍历得到的dp值为MAX(dp[a]+1,siz[a]+dp[b]+3);

将 b优先遍历得到的dp值为MAX(dp[b]+1,siz[b]+dp[a]+3);

注意到取MAX时有一个值不被顺序影响 故只需要将后面的值最优化即可

我们假设a更优 则 siz[a]+dp[b]+3<siz[b]+dp[a]+3即siz[a]-dp[a]<siz[b]-dp[b]

将子树排序后转移即可

时间复杂度粗略估计(nlogn)

代码如下

 #include <cstdio>
#include <iostream>
#include <algorithm>
#define MAXX 500005
#define MAX(a,b) (a>b?a:b)
#define r(x) x=read()
using namespace std;
typedef long long ll;
int h[MAXX],cnt,u,to;
int top,n;
ll dp[MAXX],w[MAXX],siz[MAXX];
struct node{ ll a,b;}nod[MAXX];
struct edge{int to,nex;}e[MAXX<<];
void add(int u,int to)
{
cnt++;
e[cnt]=(edge){to,h[u]};
h[u]=cnt;
}
bool cmp(node a,node b){return a.b-a.a<b.b-b.a;}
void dfs(int now,int fa)
{
if(now!=) {dp[now]=w[now];}
for(int i=h[now];i;i=e[i].nex)
{
if(e[i].to==fa) continue;
dfs(e[i].to,now);
}
for(int i=h[now];i;i=e[i].nex)
{
if(e[i].to==fa) continue;
nod[++top]=(node){dp[e[i].to],siz[e[i].to]};
}
sort(nod+,nod+top+,cmp);
for(int i=;i<=top;++i)
{
dp[now]=MAX(dp[now],siz[now]++nod[i].a);
siz[now]+=nod[i].b+;
}
if(now==) dp[now]=MAX(dp[now],siz[now]+w[now]);
top=;
}
ll read()
{
ll w=,ff=;char ch=;
while(ch<''||ch>''){if(ch=='-')ff=-;ch=getchar();}
while(ch>=''&&ch<=''){w=w*+ch-'';ch=getchar();}
return w*ff;
}
int main()
{
r(n);
for(int i=;i<=n;++i)
r(w[i]);
for(int i=;i<n;++i)
r(u),r(to),add(u,to),add(to,u);
dfs(,);
printf("%lld",dp[]);
return ;
}