[BZOJ4817][SDOI2017]树点涂色(LCT+DFS序线段树)

4817: [Sdoi2017]树点涂色

Time Limit: 10 Sec  Memory Limit: 128 MB
Submit: 692  Solved: 408
[Submit][Status][Discuss]

Description

Bob有一棵n个点的有根树,其中1号点是根节点。Bob在每个点上涂了颜色,并且每个点上的颜色不同。定义一条路

径的权值是:这条路径上的点(包括起点和终点)共有多少种不同的颜色。Bob可能会进行这几种操作:

1 x:

把点x到根节点的路径上所有的点染上一种没有用过的新颜色。

2 x y:

求x到y的路径的权值。

3 x y:

在以x为根的子树中选择一个点,使得这个点到根节点的路径权值最大,求最大权值。

Bob一共会进行m次操作

Input

第一行两个数n,m。

接下来n-1行,每行两个数a,b,表示a与b之间有一条边。

接下来m行,表示操作,格式见题目描述

1<=n,m<=100000

Output

每当出现2,3操作,输出一行。

如果是2操作,输出一个数表示路径的权值

如果是3操作,输出一个数表示权值的最大值

Sample Input

5 6
1 2
2 3
3 4
3 5
2 4 5
3 3
1 4
2 4 5
1 5
2 4 5

Sample Output

3
4
2
2

HINT

Source

鸣谢infinityedge上传

[Submit][Status][Discuss]

首先发现第一个操作很像LCT里的Access(),这个方向已经对了。

我们设f[x]表示x与父节点的值是否一样,且f[1]=1,那么对于每个点的答案就是这个点到根上的所有f[x]之和,设为g[x]。

2操作可以转化成g[x]+g[y]-2*g[lca]+1(这里的+1要考虑清楚),3操作可以转化成求x子树中的g[]的最大值,考虑如何维护g[]。

可以发现从x修改到根时,只有每次发生链的切换的时候f[]值才会改变,而每次链的切换正是LCT中Access()所执行的操作,所以我们每次Access()切换链的时候,将其子树的所有g[]加1,对新接进来的节点的子树g[]减1,这个涉及子树的操作,直接DFS+线段树解决即可。

写代码的时候突然发现自己不会区间修改区间查询的线段树了。。吃枣药丸。。

说一下标记永久化的事情,一般的标记是存放自顶向下的懒惰信息,标记自顶向下逐层下放,标记永久化则是可以看作存的是仅在这一层的懒惰信息,标记并不下放而是自底向上地合并。

一般来说标记永久化可能会短一点,速度也会快一些,但并不是非常直观,所以在不卡常数的情况下还是写普通标记吧。

先贴一份没有标记永久化的片段

void push(int x,int L,int R){
    if (!tag[x]) return;
    tag[ls]+=tag[x]; mx[ls]+=tag[x];
    tag[rs]+=tag[x]; mx[rs]+=tag[x];
    tag[x]=0;
}

void ins(int x,int L,int R,int l,int r,int k){
    if (L==l && r==R){ tag[x]+=k; mx[x]+=k; return; }
    int mid=(L+R)>>1; push(x,L,R);
    if (r<=mid) ins(ls,L,mid,l,r,k);
    else if (l>mid) ins(rs,mid+1,R,l,r,k);
        else ins(ls,L,mid,l,mid,k),ins(rs,mid+1,R,mid+1,r,k);
    mx[x]=max(mx[ls],mx[rs]);
}

int ask(int x,int L,int R,int l,int r){
    if (L==l && r==R) return mx[x];
    int mid=(L+R)>>1; push(x,L,R);
    if (r<=mid) return ask(ls,L,mid,l,r);
    else if (l>mid) return ask(rs,mid+1,R,l,r);
        else return max(ask(ls,L,mid,l,mid),ask(rs,mid+1,R,mid+1,r));
}

下面是标记永久化的程序,比原来快1/5。

  1 #include<cstdio>
  2 #include<algorithm>
  3 #define ls (x<<1)
  4 #define rs ((x<<1)|1)
  5 #define rep(i,l,r) for (int i=l; i<=r; i++)
  6 #define For(i,x) for (int i=h[x],k; i; i=nxt[i])
  7 using namespace std;
  8
  9 const int N=400100;
 10 int n,m,x,y,op,cnt,nd,tim,val[N],top[N],son[N],to[N],h[N],nxt[N];
 11 int sz[N],d[N],L[N],R[N],ch[N][2],mx[N],tag[N],f[N],fa[N];
 12 void add(int u,int v){ to[++cnt]=v; nxt[cnt]=h[u]; h[u]=cnt; }
 13 bool isroot(int x){ return (!f[x]) || (ch[f[x]][0]!=x && ch[f[x]][1]!=x); }
 14
 15 void dfs(int x,int pre){
 16     sz[x]=1; d[x]=d[pre]+1;
 17     For(i,x) if ((k=to[i])!=pre){
 18         f[k]=fa[k]=x; dfs(k,x); sz[x]+=sz[k];
 19         if (sz[son[x]]<sz[k]) son[x]=k;
 20     }
 21 }
 22
 23 void dfs2(int x,int tp){
 24     L[x]=++tim; top[x]=tp; val[tim]=d[x];
 25     if (son[x]) dfs2(son[x],tp);
 26     For(i,x) if ((k=to[i])!=fa[x] && k!=son[x]) dfs2(k,k);
 27     R[x]=tim;
 28 }
 29
 30 void rot(int x){
 31     int y=f[x],z=f[y],w=ch[y][1]==x;
 32     if (!isroot(y)) ch[z][ch[z][1]==y]=x;
 33     f[x]=z; f[y]=x; f[ch[x][w^1]]=y;
 34     ch[y][w]=ch[x][w^1]; ch[x][w^1]=y;
 35 }
 36
 37 void splay(int x){
 38     while (!isroot(x)){
 39         int y=f[x],z=f[y];
 40         if (!isroot(y)){
 41             if ((ch[z][1]==y)^(ch[y][1]==x)) rot(x); else rot(y);
 42         }
 43         rot(x);
 44     }
 45 }
 46
 47 void ins(int x,int L,int R,int l,int r,int k){
 48     if (L==l && r==R){ tag[x]+=k; mx[x]+=k; return; }
 49     int mid=(L+R)>>1;
 50     if (r<=mid) ins(ls,L,mid,l,r,k);
 51     else if (l>mid) ins(rs,mid+1,R,l,r,k);
 52         else ins(ls,L,mid,l,mid,k),ins(rs,mid+1,R,mid+1,r,k);
 53     mx[x]=max(mx[ls],mx[rs])+tag[x];
 54 }
 55
 56 int ask(int x,int L,int R,int l,int r){
 57     if (L==l && r==R) return mx[x];
 58     int mid=(L+R)>>1;
 59     if (r<=mid) return tag[x]+ask(ls,L,mid,l,r);
 60     else if (l>mid) return tag[x]+ask(rs,mid+1,R,l,r);
 61         else return tag[x]+max(ask(ls,L,mid,l,mid),ask(rs,mid+1,R,mid+1,r));
 62 }
 63
 64 int find(int x){ while (ch[x][0]) x=ch[x][0]; return x; }
 65
 66 void access(int x){
 67     for (int y=0; x; y=x,x=f[x]){
 68         splay(x); int t=find(ch[x][1]);
 69         if (t) ins(1,1,n,L[t],R[t],1);
 70         t=find(y); ch[x][1]=y;
 71         if (t) ins(1,1,n,L[t],R[t],-1);
 72     }
 73 }
 74
 75 void build(int x,int L,int R){
 76     if (L==R) { mx[x]=val[L]; return; }
 77     int mid=(L+R)>>1;
 78     build(ls,L,mid); build(rs,mid+1,R);
 79     mx[x]=max(mx[ls],mx[rs]);
 80 }
 81
 82 int get(int u,int v){
 83     for (; top[u]!=top[v]; u=fa[top[u]])
 84         if (d[top[u]]<d[top[v]]) swap(u,v);
 85     return (d[u]<d[v])?u:v;
 86 }
 87
 88 int main(){
 89     freopen("paint.in","r",stdin);
 90     freopen("paint.out","w",stdout);
 91     scanf("%d%d",&n,&m);
 92     rep(i,2,n) scanf("%d%d",&x,&y),add(x,y),add(y,x);
 93     dfs(1,0); dfs2(1,1); build(1,1,n);
 94     while (m--){
 95         scanf("%d",&op);
 96         if (op==1) scanf("%d",&x),access(x);
 97         else if (op==2){
 98             scanf("%d%d",&x,&y); int lca=get(x,y);
 99             printf("%d\n",ask(1,1,n,L[x],L[x])+ask(1,1,n,L[y],L[y])-2*ask(1,1,n,L[lca],L[lca])+1);
100         }else scanf("%d",&x),printf("%d\n",ask(1,1,n,L[x],R[x]));
101     }
102     return 0;
103 }

原文地址:https://www.cnblogs.com/HocRiser/p/8723696.html

时间: 2024-10-09 01:42:57

[BZOJ4817][SDOI2017]树点涂色(LCT+DFS序线段树)的相关文章

BZOJ.4817.[SDOI2017]树点涂色(LCT DFS序 线段树)

题目链接 1.2裸树剖,但是3.每个点的答案val很不好维护.. 如果我们把同种颜色的点划分到同一连通块中,那么向根染色的过程就是Access()! 最初所有点间都是虚边,相同颜色点用实边相连.一条边由实边变为虚边时,深度大的点所在子树所有点val+1(Access()中原先x的右儿子答案+1,因为x颜色变了): 由虚边变为实边时,深度大的点所在子树所有点val-1(fa[x]颜色与x相同导致fa[x]的贡献没了).(其实是因为 实链数量(贡献)就等于虚边数量+1?无所谓了) 于是2.就是val

Educational Codeforces Round 6 E dfs序+线段树

题意:给出一颗有根树的构造和一开始每个点的颜色 有两种操作 1 : 给定点的子树群体涂色 2 : 求给定点的子树中有多少种颜色 比较容易想到dfs序+线段树去做 dfs序是很久以前看的bilibili上电子科技大学发的视频学习的 将一颗树通过dfs编号的方式 使每个点的子树的编号连在一起作为相连的区间 就可以配合线段树搞子树 因为以前好像听说过 线段树可以解决一种区间修改和查询区间中不同的xx个数...所以一下子就想到了... 但是我不会写线段树..只会最简单的单点修改区间查询...不会用延迟标

POJ 3321 DFS序+线段树

单点修改树中某个节点,查询子树的性质.DFS序 子树序列一定在父节点的DFS序列之内,所以可以用线段树维护. 1: /* 2: DFS序 +线段树 3: */ 4:   5: #include <cstdio> 6: #include <cstring> 7: #include <cctype> 8: #include <algorithm> 9: #include <vector> 10: #include <iostream> 1

codevs1228 (dfs序+线段树)

总结: 第一次遇到dfs序的问题,对于一颗树,记录节点 i 开始搜索的序号 Left[i] 和结束搜索的序号 Righti[i],那么序号在 Left[i] ~ Right[i] 之间的都是节点 i 子树上的节点. 并且此序号与线段树中 L~R 区间对应,在纸上模拟了几遍确实如此,但暂时还未理解为何对应. 此题就是dfs序+线段树的裸题 代码: #include<iostream> #include<vector> #include<cstring> #include&

[BZOJ 3306]树(dfs序+线段树+倍增)

Description 给定一棵大小为 n 的有根点权树,支持以下操作: • 换根 • 修改点权 • 查询子树最小值 Solution 单点修改子树查询的话可以想到用dfs序+线段树来处理,换根的处理画一画图应该可以明白: 如果查询的x是当前的根rt,直接返回整棵树的min 如果rt在x的子树中,用倍增的方法找到离x最近的rt的祖先t,整棵树除t的子树以外的部分就是x当前根下的子树 如果rt不在x的子树中,查询x原来的子树的min值 #include<iostream> #include<

【BZOJ-3252】攻略 DFS序 + 线段树 + 贪心

3252: 攻略 Time Limit: 10 Sec  Memory Limit: 128 MBSubmit: 339  Solved: 130[Submit][Status][Discuss] Description 题目简述:树版[k取方格数] 众所周知,桂木桂马是攻略之神,开启攻略之神模式后,他可以同时攻略k部游戏. 今天他得到了一款新游戏<XX半岛>,这款游戏有n个场景(scene),某些场景可以通过不同的选择支到达其他场景.所有场景和选择支构成树状结构:开始游戏时在根节点(共通线)

【XSY2667】摧毁图状树 贪心 堆 DFS序 线段树

题目大意 给你一棵有根树,有\(n\)个点.还有一个参数\(k\).你每次要删除一条长度为\(k\)(\(k\)个点)的祖先-后代链,问你最少几次删完.现在有\(q\)个询问,每次给你一个\(k\),问你答案是多少. \(n\leq {10}^5,k\leq {10}^9\) 题解 设\(l\)为这棵树的叶子个数,显然当\(k>\)树的深度时答案都是\(l\). 下面要证明:答案是\(O(l+\frac{n-l}{k})\)的. 我们从下往上贪心,每次选择一个未被覆盖的深度最深的点,覆盖这个点网

codeforces 343D Water Tree 树链剖分 dfs序 线段树 set

题目链接 这道题主要是要考虑到同一棵子树中dfs序是连续的 然后我就直接上树剖了... 1 #include<bits/stdc++.h> 2 using namespace std; 3 const int MAXN=600005; 4 5 struct Node 6 { 7 int l,r; 8 int value; 9 void init() 10 { 11 l=r=value=0; 12 } 13 }tree[4*MAXN]; 14 vector<int>nei[MAXN]

【BZOJ4817】【SDOI2017】树点涂色 [LCT][线段树]

树点涂色 Time Limit: 10 Sec  Memory Limit: 128 MB[Submit][Status][Discuss] Description Bob有一棵n个点的有根树,其中1号点是根节点.Bob在每个点上涂了颜色,并且每个点上的颜色不同.定义一条路径的权值是:这条路径上的点(包括起点和终点)共有多少种不同的颜色.Bob可能会进行这几种操作: 1 x: 把点x到根节点的路径上所有的点染上一种没有用过的新颜色. 2 x y: 求x到y的路径的权值. 3 x: 在以x为根的子