原文链接 https://www.cnblogs.com/cly-none/p/9845046.html

题意:给出一棵\(n\)个结点的树,在第\(i\)个结点上有\(a_i\)个权值为\(v_i\)的物品。\(1\)号结点是根结点。你需要选出若干个物品(设选了\(t\)个),满足:

  • 如果选了结点\(i\)上的物品,那么\(i\)到根的链上每个结点都至少要选一个物品。
  • 设有选取物品的结点的最大深度为\(h\),那么\(t \leq h + k\),\(k\)为一个给定的常数。

在此基础上,你需要最大化所选的物品的权值和。

\(n \leq 2 \times 10^4, \, k \leq 5 \times 10^5, \, n \times k \leq 2.5 \times 10^7\)

显然,最终做法的复杂度应该是\(O(nk)\)的。

但这个问题比较复杂,直接想比较困难。因此,我们先考虑问题的简化版。

问题1

当第二个条件改为\(t \leq k\)时,怎么做?

对于这种一个结点的决策影响其子树的问题,我们可以对dfs序倒过来dp。确切地说,考虑当前是\(i\),那么\(i\)的子树就是\(dfn_i\)之后的一段连续区间。那么,把dfs序倒过来后,结点\(i\)就有两种可能:

  • 选了\(i\)上的物品。就是一个多重背包,从\(dp_{dfn_i + 1}\)上更新过来。
  • 不选\(i\)上的物品。那\(i\)子树中的所有物品都不能选。从\(dp_{dfn_i + sz_i}\)上更新过来。

用单调队列优化多重背包后,就能做到\(O(nk)\)。


然而,回过头来,我们依旧对\(t \leq h + k\)感到棘手。尝试按常规方法dp对\(k+h-t\)记录答案,但没有用。这个限制其实就在于,选出一条一段是根结点的链,链上每个点都取一个不计入\(t\)的物品。我们设这条链除\(1\)外的端点为\(x\)。考虑\(\forall i, \, a_i = 1\)的部分分。那么,假如我们已经确定了\(x\),则剩下的答案就是删去\(1\)到\(x\)的链,对剩下的森林做问题1的结果。

因此,我们可以考虑下面这个问题:

问题2

预处理:对于所有\(x\),删去\(x\)到根的路径后剩下的森林的问题1的答案。

博主认为,这个问题的解法相当有趣,也挺难想到的。

考虑剩下的森林的一半就是在dfs序上,从\(dfn_i + 1\)到\(n\)的一段区间(包括了\(i\)的子树)。这个部分我们在dp时就已经把答案求出来了。然而,另一部分在dfs序上既不是一段后缀,也不是连续的区间。\([1,dfn_i-1]\)中还混入了\(i\)的所有祖先。

因此,我们把这棵树左右翻转,把剩下森林的两半交换位置。也就是,再生成一个dfs序,但每个结点反序访问它的孩子结点。这样,我们就把森林的另一部分也表示为了dfs序的一个后缀。值得注意的是,\(i\)的子树不能算两次,所以这个后缀应该是[dfn_i + sz_i,n]。

这样,我们做出两个dfs序,对每个做问题1的dp,就能解决此问题。


然后就是处理\(a_i \neq 1\)的情况。上面的算法会错误,就在于\(x\)到根的路径上的结点,可能选了多个物品。那么,我们就对每个结点\(i\)建一个辅助点\(i'\),存放了\(a_i - 1\)个原来在\(i\)上的物品。这样,对于任何一个非辅助结点,它到根的路径上所有点都只有一个物品。

这样就能把最终问题转化为问题1,\(O(nk)\)地解决本题。

#include <bits/stdc++.h>
using namespace std;
const int N = 40010, K = 500010, SIZE = 51000010;
int n,k,val[N],num[N],dfn[N],sz[N],fa[N],cnt,dis[N],ans,rec[N],spadp[SIZE],spag[SIZE];
vector<int> ch[N];
int *dp[N],*g[N];
void dfs(int pos) {
sz[pos] = 1;
for (int i = 0 ; i < (int)ch[pos].size() ; ++ i) {
dfs(ch[pos][i]);
sz[pos] += sz[ch[pos][i]];
}
dfn[rec[pos] = ++cnt] = pos;
}
void fsd(int pos) {
dis[pos] += val[pos];
for (int i = (int)ch[pos].size() - 1 ; i >= 0 ; -- i) {
dis[ch[pos][i]] = dis[pos];
fsd(ch[pos][i]);
}
dfn[++cnt] = pos;
}
void update(int las,int cur) {
static int q[K],l,r;
l = 1, r = 0;
q[++r] = 0;
for (int i = 1 ; i <= k ; ++ i) {
while (l <= r && i - q[l] > num[dfn[cur]])
++ l;
if (l <= r)
dp[cur][i] = dp[las][q[l]] + val[dfn[cur]] * (i - q[l]);
else dp[cur][i] = 0;
while (l <= r && dp[las][i] > dp[las][q[r]] + val[dfn[cur]] * (i - q[r]))
-- r;
q[++r] = i;
}
}
void init() {
ans = 0;
for (int i = 0 ; i <= 2 * n ; ++ i) {
ch[i].clear();
dp[i] = spadp + i * (k + 1);
g[i] = spag + i * (k + 1);
memset(dp[i],0,sizeof(int) * (k + 1));
memset(g[i],0,sizeof(int) * (k + 1));
}
dis[1] = 0;
}
int main() {
int T;
scanf("%d",&T);
while (T --) {
scanf("%d%d",&n,&k);
init();
for (int i = 1 ; i <= n ; ++ i)
scanf("%d%d%d",&fa[i],&num[i],&val[i]);
for (int i = 2 ; i <= n ; ++ i)
ch[fa[i]].push_back(i);
for (int i = 1 ; i <= n ; ++ i) {
ch[i].push_back(i+n);
val[i+n] = val[i];
num[i+n] = num[i] - 1;
num[i] = 1;
}
cnt = 0;
dfs(1);
for (int i = 1 ; i <= 2 * n ; ++ i) {
update(i-1,i);
for (int j = 1 ; j <= k ; ++ j)
dp[i][j] = max(dp[i][j],dp[i - sz[dfn[i]]][j]), dp[i][j] = max(dp[i][j],dp[i][j-1]);
}
for (int i = 1 ; i <= 2 * n ; ++ i)
for (int j = 1 ; j <= k ; ++ j)
g[i][j] = dp[i][j];
cnt = 0;
fsd(1);
for (int i = 1 ; i <= 2 * n ; ++ i) {
update(i-1,i);
for (int j = 1 ; j <= k ; ++ j)
dp[i][j] = max(dp[i][j],dp[i - sz[dfn[i]]][j]), dp[i][j] = max(dp[i][j],dp[i][j-1]);
}
for (int i = 1 ; i <= 2 * n ; ++ i) {
if (dfn[i] > n) continue;
int p = rec[dfn[i]] - sz[dfn[i]];
for (int j = 0 ; j <= k ; ++ j)
ans = max(ans,dis[dfn[i]] + dp[i-1][j] + g[p][k-j]);
}
printf("%d\n",ans);
}
return 0;
}

小结:一道对dfs序上dp进行拓展的好题。当一个问题分成了性质相同的两半,而前者容易解决,后者难以解决的问题时,寻找方式来交换这两部分的位置,最后合并。这个思路应该记住。

【做题】SDOI2017苹果树——dfs序的运用的更多相关文章

  1. CODEVS.1228 苹果树(DFS序)

    To CODEVS.1228 苹果树  To poj 3321 Description 在卡卡的房子外面,有一棵苹果树.每年的春天,树上总会结出很多的苹果.卡卡非常喜欢吃苹果,所以他一直都精心的呵护这 ...

  2. poj3321 dfs序+树状数组单点更新 好题!

    当初听郭炜老师讲时不是很懂,几个月内每次复习树状数组必看的题 树的dfs序映射在树状数组上进行单点修改,区间查询. /* 树状数组: lowbit[i] = i&-i C[i] = a[i-l ...

  3. hdu 5692(dfs序+线段树,好题)

    Snacks Time Limit: 10000/5000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others)Total Sub ...

  4. BZOJ2434[Noi2011]阿狸的打字机——AC自动机+dfs序+树状数组

    题目描述 阿狸喜欢收藏各种稀奇古怪的东西,最近他淘到一台老式的打字机.打字机上只有28个按键,分别印有26个小写英文字母和'B'.'P'两个字母. 经阿狸研究发现,这个打字机是这样工作的: l 输入小 ...

  5. BZOJ 3252题解(贪心+dfs序+线段树)

    题面 传送门 分析 此题做法很多,树形DP,DFS序+线段树,树链剖分都可以做 这里给出DFS序+线段树的代码 我们用线段树维护到根节点路径上节点权值之和的最大值,以及取到最大值的节点编号x 每次从根 ...

  6. poj3321-Apple Tree(DFS序+树状数组)

    Apple Tree Time Limit: 2000MS   Memory Limit: 65536K Total Submissions: 36442   Accepted: 10894 Desc ...

  7. cf276E 两棵线段树分别维护dfs序和bfs序,好题回头再做

    搞了一晚上,错了,以后回头再来看 /* 对于每次更新,先处理其儿子方向,再处理其父亲方向 处理父亲方向时无法达到根,那么直接更新 如果能达到根,那么到兄弟链中去更新,使用bfs序 最后,查询结点v的结 ...

  8. (好题)树状数组+离散化+DFS序+离线/莫队 HDOJ 4358 Boring counting

    题目传送门 题意:给你一棵树,树上的每个节点都有树值,给m个查询,问以每个点u为根的子树下有多少种权值恰好出现k次. 分析:首先要对权值离散化,然后要将树形转换为线形,配上图:.然后按照右端点从小到大 ...

  9. 【NOI2019集训题2】 序列 后缀树+splay+dfs序

    题目大意:给你一个长度为$n$的序列$a_i$,还有一个数字$m$,有$q$次询问 每次给出一个$d$和$k$,问你对所有的$a_i$都在模$m$意义下加了$d$后,第$k$小的后缀的起点编号. 数据 ...

随机推荐

  1. Oracle课程档案,第二天

    salary:工资 order by:排序 desc:降序 hire:雇佣 单行函数 一周有七天 一月不一定只有30天 trunc:截取 dual:空表 last:最后 month:月份 round: ...

  2. 在linux中安装selenium+chrome

    主要参照百度的一些内容加上自己的实际操作,对自己遇到的几个问题进行总结: 第一个问题:安装selenium---sudo pip install selenium 显示:You are using p ...

  3. python中元组与数组的区别

    列表: a=['12', '3rr'] 元组: t=(21,34) 列表可以修改,而元组不可以修改,如果元组中仅有一个元素,则要在元素后加上逗号. 元组和列表的查询方式一样. 元组只可读不可修改. 如 ...

  4. java 中重写toString()方法

    toString()方法 一般出现在System.out.println(类名.toString()); toString()是一种自我描述方法 本身返回的是 getClass().getName() ...

  5. python下载网页视频

    因网站不同需要修改. 下载 mp4 连接 from bs4 import BeautifulSoup import requests import urllib import re import js ...

  6. 设计模式之——bridge模式

    Bridge模式,又叫桥接模式,是针对同一接口进行扩展与实现操作的一种设计模式. 这种模式,与之前学过的适配器模式具有相似的地方,也有不同的地方,下面就让我们一一解析吧. 首先,我们要了解到,为什么需 ...

  7. QSS独门秘籍:subcontrol

    QSS是C++ Qt中的界面美化神器,其语法和CSS区别不大,但是QSS有一个独有的功能——subcontrol,这是CSS所没有的,一个widget往往由多个子部件构成,利用subcontrol可以 ...

  8. git 用远程覆盖本地

    git 用远程覆盖本地   git fetch --all git reset --hard origin/master

  9. async await 的使用。 其实就是和then一样,只不过改变了链式写法

    这样写显得更加舒服.

  10. window中普通用户无法登录远程桌面

    解决方案就是将该用户加到 Remote Desktop Users 这个用户组中. 使用命令 net localgroup "Remote Desktop Users" 用户名 / ...