@description@

你天天努力,还是比不上小牛,因为小牛在家中套路。于是你决定去拜访小牛,以请教套路的方法。

小牛住在长满多汁牧草的大草原中,草原上共有 n 个牧场,n−1 条双向道路连接这些牧场使得牧场之间两两可达。通过一条道路需要花费一定的时间,一开始这个值都是 0。

小牛并不想让你找到他,所以小牛有时候会通过一些方式使得通过某条道路的时间发生变化。于是你被小牛弄得晕头转向,不知所措。

最后你放弃了寻找小牛,开始研究起一个问题:任选两个牧场 u 和 v ,定义 dis(u,v) 为 u 到 v 的最短路,那么 dis(u,v) 最大可以是多少呢?

由于小牛操纵着牧场道路通过时间的变化,所以你需要在小牛每次操作后都重新求出这个问题的答案。

@Input format@

第一行两个数 n,m,表示牧场的数量和小牛操作的数量。

第二行 n−1 个数,第 i 个数是 f[i+1](f[i+1]≤i),代表在 f[i+1]号牧场和 i+1 号牧场之间有一条双向道路,通过时间为 0。

接下来 m 行,每行两个数 pi,wi,表示将 f[pi] 号牧场和 pi 号牧场之间的双向道路的通过时间改为 wi。

@Output format@

输出共 m 行,第 i 行输出一个数表示小牛第 i 次操作后 dis(u,v)的最大值。

@Sample input@

5 4

1 1 2 2

4 8

4 3

2 2

5 7

@Sample output@

8

3

5

10

@Constraints@

对于所有的数据,n,m≤1e5,0≤wi≤2e3。

@solution@

简单来说就是在支持修改边权的操作下动态维护直径长度。

嗯。虽然我知道求直径是一个经典的 dp 问题,而这道题就是一道经典的动态 dp 问题。

不过我尝试着寻找一种比动态 dp 好写的算法(虽然写出来跟动态 dp 长度差不多。。。)。

求直径还有另一个经典的算法:点分治两次搜索。

第一次,找到距离根最远的点,不妨令其为 x。

第二次,找到距离 x 最远的点,不妨令其为 y。于是 x 与 y 的距离就是直径长。

于是我们尝试动态维护出两次搜索需要的信息。

第一次搜索,只需要支持子树的加减、查询最大值的操作即可。可以在 dfs 序上直接建一棵线段树。

第二次搜索,因为我们只需要求直径长,所以没必要维护出点 y。

因为点 x 是一个叶子结点(可能有多个结点满足离根最远,但不难发现总存在一个叶子结点满足条件),所以以点 x 为端点的直径肯定是从 x 往上爬至一定位置然后向下走。

我们再搞出两个线段树。其中一棵用于维护重链:维护重链上的点“通过轻儿子向下可到达的最远距离 - 这个点离根的距离的最大值”;另一棵维护每一个点下面挂着的轻儿子们,维护“通过这个点的轻儿子向下可到达的最远距离与次远距离”(其实这个可以不用线段树维护,只是因为我前两个需要就封装了一个,刚好就用一下。。。)。

查询时顺着重链往上时,如果遇到重链就访问我们建的第二棵线段树,遇到轻边就访问我们建的第三棵线段树,对答案进行更新。

修改时对应上面的定义恰当修改即可。不再赘述。

时间复杂度 O(nlog^2 n)

@accepted code@

#include<cstdio>
#include<iostream>
using namespace std;
#define mp make_pair
typedef pair<int, int> pii;
const int MAXN = 100000;
struct edge{
edge *nxt; int to;
}edges[MAXN + 5], *adj[MAXN + 5], *ecnt=&edges[0];
void addedge(int u, int v) {
edge *p = (++ecnt);
p->to = v, p->nxt = adj[u], adj[u] = p;
}
struct segtree{
struct node{
int le, ri, tag;
pii mx, smx;
}tree[4*MAXN + 5];
bool comp(pii a, pii b) {
if( a.first == 0 ) return false;
if( b.first == 0 ) return true;
if( a.second == b.second ) return a.first > b.first;
return a.second > b.second;
}// a > b : true
void update(pii &mx, pii &smx, pii x) {
if( comp(x, mx) ) smx = mx, mx = x;
else if( comp(x, smx) ) smx = x;
}
void pushup(int x) {
tree[x].mx = tree[x<<1|1].mx, tree[x].smx = tree[x<<1|1].smx;
update(tree[x].mx, tree[x].smx, tree[x<<1].mx);
update(tree[x].mx, tree[x].smx, tree[x<<1].smx);
}
void pushdown(int x) {
if( tree[x].tag ) {
tree[x<<1].mx.second += tree[x].tag, tree[x<<1].smx.second += tree[x].tag, tree[x<<1].tag += tree[x].tag;
tree[x<<1|1].mx.second += tree[x].tag, tree[x<<1|1].smx.second += tree[x].tag, tree[x<<1|1].tag += tree[x].tag;
tree[x].tag = 0;
}
}
void build(int x, int l, int r) {
tree[x].le = l, tree[x].ri = r, tree[x].tag = 0;
if( l >= r ) {
tree[x].mx = mp(l, 0), tree[x].smx = mp(0, 0);
return ;
}
int mid = (l + r) >> 1;
build(x<<1, l, mid), build(x<<1|1, mid + 1, r);
pushup(x);
}
void add(int x, int l, int r, int d) {
if( l > tree[x].ri || r < tree[x].le )
return ;
if( l <= tree[x].le && tree[x].ri <= r ) {
tree[x].tag += d, tree[x].mx.second += d, tree[x].smx.second += d;
return ;
}
pushdown(x);
add(x<<1, l, r, d);
add(x<<1|1, l, r, d);
pushup(x);
}
void modify(int x, int p, int k) {
if( p > tree[x].ri || p < tree[x].le )
return ;
if( tree[x].le == tree[x].ri ) {
tree[x].mx.second = k;
return ;
}
pushdown(x);
modify(x<<1, p, k);
modify(x<<1|1, p, k);
pushup(x);
}
pii query_max(int x, int l, int r) {
if( l > tree[x].ri || r < tree[x].le )
return mp(0, 0);
if( l <= tree[x].le && tree[x].ri <= r )
return tree[x].mx;
pushdown(x);
pii a = query_max(x<<1, l, r), b = query_max(x<<1|1, l, r);
return comp(a, b) ? a : b;
}
int query_key(int x, int p) {
if( tree[x].le == tree[x].ri )
return tree[x].mx.second;
int mid = (tree[x].le + tree[x].ri) >> 1;
pushdown(x);
if( p <= mid ) return query_key(x<<1, p);
else return query_key(x<<1|1, p);
}
pair<pii, pii> query_sec_max(int x, int l, int r) {
if( l > tree[x].ri || r < tree[x].le )
return mp(mp(0, 0), mp(0, 0));
if( l <= tree[x].le && tree[x].ri <= r )
return mp(tree[x].mx, tree[x].smx);
pushdown(x);
pair<pii, pii> a = query_sec_max(x<<1, l, r);
pair<pii, pii> b = query_sec_max(x<<1|1, l, r);
update(a.first, a.second, b.first);
update(a.first, a.second, b.second);
return a;
}
}T1, T2, T3;
int f[MAXN + 5], w[MAXN + 5], n, m;
int siz[MAXN + 5], hvy[MAXN + 5];
int dfn[MAXN + 5], tid[MAXN + 5], top[MAXN + 5], dcnt = 0;
int num[MAXN + 5], arr[MAXN + 5], le[MAXN + 5], ri[MAXN + 5], tot = 0;
void read() {
scanf("%d%d", &n, &m);
for(int i=2;i<=n;i++)
scanf("%d", &f[i]);
}
void func() {
for(int i=2;i<=n;i++)
addedge(f[i], i);
for(int i=n;i>=2;i--)
siz[f[i]] += (++siz[i]);
siz[1]++;
for(int i=n;i>=1;i--)
if( siz[hvy[f[i]]] < siz[i] )
hvy[f[i]] = i;
}
void dfs(int x, int tp) {
dfn[++dcnt] = x, tid[x] = dcnt, top[x] = tp;
if( !hvy[x] ) return ;
dfs(hvy[x], tp);
for(edge *p=adj[x];p;p=p->nxt)
if( p->to != hvy[x] ) {
arr[++tot] = p->to;
if( !le[x] ) le[x] = tot;
num[p->to] = ri[x] = tot;
}
for(edge *p=adj[x];p;p=p->nxt)
if( p->to != hvy[x] )
dfs(p->to, p->to);
}
inline int dist(int x, int y) {
return T1.query_key(1, tid[x]) - T1.query_key(1, tid[y]);
}
inline int find_max(int x, int y) {
return T1.query_max(1, tid[y], tid[y]+siz[y]-1).second - T1.query_key(1, tid[x]);
}
void modify(int x, int k) {
int del = k - w[x]; w[x] = k;
T1.add(1, tid[x], tid[x]+siz[x]-1, del), T2.add(1, tid[x], tid[x]+siz[x]-1, -del);
while( true ) {
x = top[x];
if( f[x] ) {
int d = T3.query_max(1, le[f[x]], ri[f[x]]).second;
T3.modify(1, num[x], find_max(f[x], x));
d = T3.query_max(1, le[f[x]], ri[f[x]]).second - d;
T2.add(1, tid[f[x]], tid[f[x]], d);
x = f[x];
}
else break;
}
}
int query() {
int x = dfn[T1.query_max(1, 1, n).first], dis = 0, ret = 0;
while( true ) {
int p = dfn[T2.query_max(1, tid[top[x]], tid[x] - 1).first];
ret = max(ret, dis + dist(x, p) + T3.query_max(1, le[p], ri[p]).second);
dis += dist(x, top[x]), x = top[x];
if( f[x] ) {
dis += w[x];
ret = max(ret, dis + find_max(f[x], hvy[f[x]]));
pair<pii, pii> p = T3.query_sec_max(1, le[f[x]], ri[f[x]]);
if( arr[p.first.first] == x ) ret = max(ret, dis + p.second.second);
else ret = max(ret, dis + p.first.second);
x = f[x];
}
else break;
}
return ret;
}
void solve() {
T1.build(1, 1, dcnt), T2.build(1, 1, dcnt), T3.build(1, 1, tot);
for(int i=1;i<=m;i++) {
int p, w; scanf("%d%d", &p, &w);
modify(p, w), printf("%d\n", query());
}
}
int main() {
read(), func(), dfs(1, 1), solve();
}

@details@

至少通过这道题我懂得了一个道理:千万不要在其他题不能确保正确性(比如对拍、手造数据、写暴力)的情况下写数据结构题。

否则你就有可能会遭遇其他题写挂,数据结构题也写挂的双挂结局。

而且写数据结构题,测完样例并没有什么用。即使没时间对拍,也要手造数据尝试 hack 自己。

@noi.ac - 441@ 你天天努力的更多相关文章

  1. # NOI.AC省选赛 第五场T1 子集,与&最大值

    NOI.AC省选赛 第五场T1 A. Mas的童年 题目链接 http://noi.ac/problem/309 思路 0x00 \(n^2\)的暴力挺简单的. ans=max(ans,xor[j-1 ...

  2. NOI.ac #31 MST DP、哈希

    题目传送门:http://noi.ac/problem/31 一道思路好题考虑模拟$Kruskal$的加边方式,然后能够发现非最小生成树边只能在一个已经由边权更小的边连成的连通块中,而树边一定会让两个 ...

  3. NOI.AC NOIP模拟赛 第五场 游记

    NOI.AC NOIP模拟赛 第五场 游记 count 题目大意: 长度为\(n+1(n\le10^5)\)的序列\(A\),其中的每个数都是不大于\(n\)的正整数,且\(n\)以内每个正整数至少出 ...

  4. NOI.AC NOIP模拟赛 第六场 游记

    NOI.AC NOIP模拟赛 第六场 游记 queen 题目大意: 在一个\(n\times n(n\le10^5)\)的棋盘上,放有\(m(m\le10^5)\)个皇后,其中每一个皇后都可以向上.下 ...

  5. NOI.AC NOIP模拟赛 第二场 补记

    NOI.AC NOIP模拟赛 第二场 补记 palindrome 题目大意: 同[CEOI2017]Palindromic Partitions string 同[TC11326]Impossible ...

  6. NOI.AC NOIP模拟赛 第一场 补记

    NOI.AC NOIP模拟赛 第一场 补记 candy 题目大意: 有两个超市,每个超市有\(n(n\le10^5)\)个糖,每个糖\(W\)元.每颗糖有一个愉悦度,其中,第一家商店中的第\(i\)颗 ...

  7. NOI.AC NOIP模拟赛 第四场 补记

    NOI.AC NOIP模拟赛 第四场 补记 子图 题目大意: 一张\(n(n\le5\times10^5)\)个点,\(m(m\le5\times10^5)\)条边的无向图.删去第\(i\)条边需要\ ...

  8. NOI.AC NOIP模拟赛 第三场 补记

    NOI.AC NOIP模拟赛 第三场 补记 列队 题目大意: 给定一个\(n\times m(n,m\le1000)\)的矩阵,每个格子上有一个数\(w_{i,j}\).保证\(w_{i,j}\)互不 ...

  9. NOI.AC WC模拟赛

    4C(容斥) http://noi.ac/contest/56/problem/25 同时交换一行或一列对答案显然没有影响,于是将行列均从大到小排序,每次处理限制相同的一段行列(呈一个L形). 问题变 ...

随机推荐

  1. Mac下载Navicat premium提示文件损坏的解决方案

    首先打开终端,执行: sudo bash 这时会提示你输入你的账户密码, 输入完后就切换到了 root 用户,然后执行: xattr -cr /Applications/Navicat\ Premiu ...

  2. Win7。56个进程让我头疼

    乱七八糟的进程一个一个往外蹦,如此痛苦. 安装了一个VM9,进程数量+5,安装了卖咖啡的,进程数量+5. 除去这10个,系统进程数量还有46个....还是太多... 64位系统,真的很痛苦,还没有怎么 ...

  3. linux-基础-常用命令

    一 Linux的简介 1.1 Linux的概述 Linux是基于Unix的开源免费的操作系统,由于系统的稳定性和安全性几乎成为程序代码运行的最佳系统环境.Linux是由Linus Torvalds(林 ...

  4. 洛谷P1147 连续自然数和 [2017年6月计划 数论01]

    P1147 连续自然数和 题目描述 对一个给定的自然数M,求出所有的连续的自然数段,这些连续的自然数段中的全部数之和为M. 例子:1998+1999+2000+2001+2002 = 10000,所以 ...

  5. js中this指向学习总结

      在面向对象的语言中(例如Java,C#等),this 含义是明确且具体的,即指向当前对象.一般在编译期绑定. 然而js中this 是在运行期进行绑定的,这是js中this 关键字具备多重含义的本质 ...

  6. 阿里云合作伙伴峰会SaaS加速器专场 | 商业加持,迈进亿元俱乐部

    导语:本文中,阿里云智能运营专家朱以军从宏观角度分析了SaaS市场的机遇和挑战,重点介绍了阿里云的商业操作系统.同时,阿里云SaaS加速器也在招募更多ISV合作伙伴和我们一起共创专注面向未来的应用,用 ...

  7. 蚁群算法MATLAB解VRP问题

    Excel  exp12_3_2.xls内容: ANT_VRP函数: function [R_best,L_best,L_ave,Shortest_Route,Shortest_Length]=ANT ...

  8. iOS自动化打包上传的踩坑记

    http://www.cocoachina.com/ios/20160624/16811.html 很久以前就看了很多关于iOS自动打包ipa的文章, 看着感觉很简单, 但是因为一直没有AppleDe ...

  9. jsp之jstl(展示所有商品、重写登录案例)

    jsp之jstl jstl: jsp标准的标签库语言,apache的,是用来替代java脚本 使用步骤: 1.导入jar包 (jstl.jar和standard.jar) 2.在页面上导入标签库 &l ...

  10. vagrant up 时提示错误 cound not open file

    根据教程:https://laravel-china.org/docs/laravel-development-environment/5.5/development-environment-wind ...