@description@

给定 p 为 0~N-1 的一个排列,并给定一棵 N 个点的树。

我们称一个包含 L 个结点的路径是“漂亮”的,当且仅当对于 0 ≤ i ≤ L-1,路径都存在 v 使得 p[v] = i,一棵树的“漂亮程度”被定义为其包含的“漂亮”路径数量。

现给定 M 次操作,每次交换 p[u] 与 p[v],询问每次操作完后当前树的“漂亮程度”。

输入格式

输入的第一行包含一个整数 N,第二行包含 N 个整数 p1,p2,...,pN。

接下来 N −1 行,每行包含两个整数 u 和 v,代表节点 u 和 v 之间有一条边。 接下来一行包含一个整数 M。 接下来 M 行,每行包含两个整数 u 和 v,代表一次操作。

输出格式

对于每组数据,输出一行,包含一个整数,代表操作后树的漂亮程度。

数据范围与子任务

• 1 ≤ N,M ≤ 5·10^5

• 0 ≤ pi ≤ N −1

• 1 ≤ u,v ≤ N

样例数据

输入

5

2 0 1 3 4

2 4

1 2

5 2

1 3

4

2 1

5 5

3 4

3 2

输出

4

4

3

2

@solution@

本道题和 IOI2018 那道题的排座位其实很像。。。

就是找一些特征值可以代表树上的一条链,然后维护一下。。。

直接避免这些抽象的东西,我们切入正题。

先考虑给定 L,怎么判断长度为 L 的“漂亮”路径是否存在。

一个链在有根树上有两种形态,一种由祖先连向子孙,一种形如 '^'。

对于祖先连向子孙这类形态,可以发现它有两个“特殊结点”:最上面那个,没有连向父亲的边;最下面那个,没有连向儿子的边。

稍微思考一下就可以发现,我们如果限制没有连向儿子的边的特殊点个数为 1,则可能的形态只有一种。

考虑维护特殊点个数。如果一个点有连向儿子的边,肯定会先选择到连 p 最小的儿子。

于是:我们维护一个计数器 cnt = L;依次扫描 p[i]=0...L-1 的点,如果它的最小儿子 < L 则 cnt--。最终剩下 cnt = 1 则说明是我们想要的形态。

对于 '^' 型的链,依然还是先限制没有连向儿子的边的特殊点个数为 2。此时可能会出现三种形态:不相交的两条链、一条链、一条链然后 lca 往上延伸出了一个枝末。

我们依次排除掉另两种情况。考虑找到一个 L' 使得存在一个点 x 使得它、它的最小儿子、它的次小儿子 < L'。假如我保证 L >= L',则就可以排除掉第一个情况。

考虑找到这个 x,得到它父亲的 p 值为 k。假如我保证 L < k 既可以排除掉第三个情况。

考虑怎么维护。首先开个 set 维护一个点 x 的儿子(其实更合理地应该是使用可删除任意结点的堆,但是我不会写。。。),方便我们得到一个点的最小儿子 k1,次小儿子 k2。

维护一棵线段树表示我们的计数器,一开始线段树中位置 i 的值为 i,每个点 x 在 max(k1, p[x]) ~ N 中减一。

看起来我们需要维护一个值的出现次数,但是注意到 cnt = 1/2 都是对应情况 cnt 的最小可能取值,于是维护一下最小值与最小值的出现次数即可。

最后全局开一个 set 维护 max(k2, p[x]) 以及对应的 x,便于我们处理第二种情况。

交换的时候按照定义相应地改一改即可。时间复杂度 O(nlogn)。

@accepted code@

#include<set>
#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
typedef pair<int, int> pii;
const int MAXN = 500000;
const int INF = (1<<30);
struct edge{
edge *nxt; int to;
}edges[2*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;
p = (++ecnt);
p->to = u, p->nxt = adj[v], adj[v] = p;
}
struct segtree{
struct node{
int l, r, tg;
pii mn;
}t[4*MAXN + 5];
pii merge(pii a, pii b) {
pii ret = make_pair(min(a.first, b.first), 0);
if( a.first == ret.first ) ret.second += a.second;
if( b.first == ret.first ) ret.second += b.second;
return ret;
}
void pushup(int x) {
t[x].mn = merge(t[x << 1].mn, t[x << 1 | 1].mn);
}
void pushdown(int x) {
if( t[x].tg ) {
t[x << 1].tg += t[x].tg, t[x << 1 | 1].tg += t[x].tg;
t[x << 1].mn.first += t[x].tg, t[x << 1| 1].mn.first += t[x].tg;
t[x].tg = 0;
}
}
void build(int x, int l, int r) {
t[x].l = l, t[x].r = r, t[x].tg = 0;
if( l == r ) {
t[x].mn = make_pair(l, 1);
return ;
}
int mid = (l + r) >> 1;
build(x << 1, l, mid), build(x << 1 | 1, mid + 1, r);
pushup(x);
}
void modify(int x, int l, int r, int d) {
if( l > t[x].r || r < t[x].l )
return ;
if( l <= t[x].l && t[x].r <= r ) {
t[x].tg += d, t[x].mn.first += d;
return ;
}
pushdown(x);
modify(x << 1, l, r, d);
modify(x << 1 | 1, l, r, d);
pushup(x);
}
pii query(int x, int l, int r) {
if( l > t[x].r || r < t[x].l )
return make_pair(INF, 0);
if( l <= t[x].l && t[x].r <= r )
return t[x].mn;
pushdown(x);
return merge(query(x << 1, l, r), query(x << 1 | 1, l, r));
}
}T;
set<int>st[MAXN + 5];
set<pii>st2;
int p[MAXN + 5], fa[MAXN + 5], N, M;
void dfs(int x, int f) {
fa[x] = f;
for(edge *q=adj[x];q;q=q->nxt) {
if( q->to == f ) continue;
dfs(q->to, x);
st[x].insert(p[q->to]);
}
}
int f[MAXN + 5], g[MAXN + 5];
void update(int x) {
set<int>::iterator it = st[x].begin();
if( it != st[x].end() ) {
if( f[x] == 0 ) f[x] = max(p[x], *it), T.modify(1, f[x], N, -1);
else {
if( f[x] != max(p[x], *it) ) {
if( f[x] < max(p[x], *it) )
T.modify(1, f[x], max(p[x], *it)-1, 1);
else T.modify(1, max(p[x], *it), f[x]-1, -1);
f[x] = max(p[x], *it);
}
}
it++;
if( it != st[x].end() ) {
if( g[x] == 0 ) g[x] = max(p[x], *it), st2.insert(make_pair(g[x], x));
else {
if( g[x] != max(p[x], *it) ) {
st2.erase(make_pair(g[x], x));
g[x] = max(p[x], *it);
st2.insert(make_pair(g[x], x));
}
}
}
}
}
void update(int &x, pii p, int k) {
if( p.first == k ) x += p.second;
}
int get_ans() {
int ret = 0; update(ret, T.query(1, 1, N), 1);
set<pii>::iterator it = st2.begin();
if( it != st2.end() ) {
if( fa[it->second] ) update(ret, T.query(1, it->first, p[fa[it->second]]-1), 2);
else update(ret, T.query(1, it->first, N), 2);
}
return ret;
}
int main() {
scanf("%d", &N);
for(int i=1;i<=N;i++)
scanf("%d", &p[i]), p[i]++;
for(int i=1;i<N;i++) {
int u, v; scanf("%d%d", &u, &v);
addedge(u, v);
}
dfs(1, 0); T.build(1, 1, N);
for(int i=1;i<=N;i++)
update(i);
scanf("%d", &M);
for(int i=1;i<=M;i++) {
int u, v; scanf("%d%d", &u, &v);
if( fa[u] ) st[fa[u]].erase(p[u]);
if( fa[v] ) st[fa[v]].erase(p[v]);
swap(p[u], p[v]);
if( fa[u] ) st[fa[u]].insert(p[u]);
if( fa[v] ) st[fa[v]].insert(p[v]);
update(u), update(v);
if( fa[u] ) update(fa[u]);
if( fa[v] ) update(fa[v]);
printf("%d\n", get_ans());
}
}

@details@

事实上。。。写得不好 set 用得太多的话。。。是会被卡常的。。。

@codechef - SONATR@ Sonya and Tree的更多相关文章

  1. BZOJ 3221: [Codechef FEB13] Obserbing the tree树上询问( 可持久化线段树 + 树链剖分 )

    树链剖分+可持久化线段树....这个一眼可以看出来, 因为可持久化所以写了标记永久化(否则就是区间修改的线段树的持久化..不会), 结果就写挂了, T得飞起...和管理员拿数据调后才发现= = 做法: ...

  2. codechef Prime Distance On Tree(树分治+FFT)

    题目链接:http://www.codechef.com/problems/PRIMEDST/ 题意:给出一棵树,边长度都是1.每次任意取出两个点(u,v),他们之间的长度为素数的概率为多大? 树分治 ...

  3. Codechef:Path Triples On Tree

    Path Triples On Tree 题意是求树上都不相交或者都相交的路径三元组数量. 发现blog里没什么树形dp题,也没有cc题,所以来丢一道cc上的树形dp题. 比较暴力,比较恶心 #inc ...

  4. [BZOJ 3221][Codechef FEB13] Obserbing the tree树上询问

    [BZOJ 3221]Obserbing the tree树上询问 题目 小N最近在做关于树的题.今天她想了这样一道题,给定一棵N个节点的树,节点按1~N编号,一开始每个节点上的权值都是0,接下来有M ...

  5. Codechef TSUM2 Sum on Tree 点分治、李超线段树

    传送门 点分治模板题都不会迟早要完 发现这道题需要统计所有路径的信息,考虑点分治统计路径信息. 点分治之后,因为路径是有向的,所以对于每一条路径都有向上和向下的两种.那么如果一条向上的路径,点数为\( ...

  6. 【CodeChef】QTREE- Queries on tree again!

    题解 给你一棵基环树,环长为奇数(两点间最短路径只有一条) 维护两点间路径最大子段和,支持把一条路径上的值取反 显然只要断开一条边维护树上的值,然后对于那条边分类讨论就好了 维护树上的值可以通过树链剖 ...

  7. codechef T6 Pishty and tree dfs序+线段树

    PSHTTR: Pishty 和城堡题目描述 Pishty 是生活在胡斯特市的一个小男孩.胡斯特是胡克兰境内的一个古城,以其中世纪风格 的古堡和非常聪明的熊闻名全国. 胡斯特的镇城之宝是就是这么一座古 ...

  8. Codechef Prime Distance On Tree

    [传送门] FFT第四题! 暑假的时候只会点分,然后合并是暴力合并的...水过去了... 其实两条路径长度的合并就是卷积的过程嘛,每次统计完路径就自卷积一下. 刚开始卷积固定了值域.T了.然后就不偷懒 ...

  9. @codechef - TREEPATH@ Decompose the Tree

    目录 @description@ @solution@ @accepted code@ @details@ @description@ 给定一棵无根树,每个节点上都写了一个整数. 你的任务就是统计有多 ...

随机推荐

  1. [jnhs]教训之jsp页面无法用jstl取值的坑.真他妈的奇葩,实体类的属性名不能用大写

    结果页面永远都是空 调试发现,数据正常的塞进去了 问题解决: https://zhidao.baidu.com/question/570584436.html 实体类的属性名,首字母不能大写,改成小写 ...

  2. IO流8 --- 使用FileReader和FileWriter实现文本文件的复制 --- 技术搬运工(尚硅谷)

    @Test public void test4(){ FileReader fr = null; FileWriter fw = null; try { fr = new FileReader(&qu ...

  3. free内存监控

    语 法: free [-bkmotV][-s <间隔秒数>] 补充说明:free指令会显示内存的使用情况,包括实体内存,虚拟的交换文件内存,共享内存区段,以及系统核心使用的缓冲区等. 参 ...

  4. oracle -视图 序列 约束

    1.视图 视图是基于一个或者多个表数据库对象,视图允许用户创建一个无数据的”伪表“,视图只是一个获取特定列好行的sql查询组成,通过视图检索数据就像从表中检索数据 一样. 视图可以提供一个附加的安全层 ...

  5. Vim 中自定义注释快捷键

    写程序的时候写过的代码不忍心立马删掉,所以注释很多,所以找了下注释的快捷健. 打开 /etc/vim/vimrc文件,添加如下两行代码即可. /* 注释该行 */ map = I/* ^[A */j ...

  6. Congratulation!顺利通过-2019年6月份的PMP考试

    祝贺邮件 证书

  7. 2018-5-22-SublimeText-粘贴图片保存到本地

    title author date CreateTime categories SublimeText 粘贴图片保存到本地 lindexi 2018-05-22 15:15:26 +0800 2018 ...

  8. 【机器学习PAI实战】—— 玩转人工智能之美食推荐

    前言 在生活中,我们经常给朋友推荐一些自己喜欢的东西,也时常接受别人的推荐.怎么能保证推荐的电影或者美食就是朋友喜欢的呢?一般来说,你们两个人经常对同一个电影或者美食感兴趣,那么你喜欢的东西就很大程度 ...

  9. 如何利用 Webshell 诊断 EDAS Serverless 应用

    本文主要介绍 Serverless 应用的网络环境以及 Serverless 应用容器内的环境,了解背景知识以及基本的运维知识后可以利用 Webshell 完成基本的运维需求. Webshell 简介 ...

  10. scala2.11读取文件

    1.读取行 要读取文件的所有行,可以调用scala.io.Source对象的getLines方法: import scala.io.Source val source = Source.fromFil ...