zoukankan      html  css  js  c++  java
  • [LOJ2001] 树点染色

    问题描述

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

    定义一条路径的权值是,这条路径上的点(包括起点和终点)共有多少种不同的颜色。

    Bob 可能会进行这几种操作:

    • 1 x,把点 x 到根节点的路径上的所有的点染上一种没有用过的新颜色;
    • 2 x y,求 x 到 y 的路径的权值;
    • 3 x,在以 x 为根的子树中选择一个点,使得这个点到根节点的路径权值最大,求最大权值。

    Bob 一共会进行 m 次操作。

    输入格式

    第一行两个数 n、m。
    接下来 n−1 行,每行两个数 a、b 表示 a 和 b 之间有一条边。
    接下来 m 行,表示操作,格式见题目描述。

    输出格式

    每当出现 23 操作,输出一行。

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

    样例输入

    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

    样例输出

    3
    4
    2
    2

    数据范围

    对所有数据,(1le n,m le 10^5)。。

    解析

    观察题目所给的操作,我们可以发现,树上的每一种颜色都是深度递增的连续的一段。我们接下来的处理方法都是基于这个性质的。

    对于操作1,直接将一个点到根节点的路径染色,我们可以直接联想到LCT的access操作。因此我们只需用LCT维护颜色集合即可。

    对于操作2,回答一条路径上的颜色种类,我们可以用LCA求两点之间路径长的方法,在LCT中维护每个点到根节点经过的颜色种类数。答案就是两端点的值之和减去两倍的LCA的值再加上1,因为重复减去了LCA上的颜色。

    但如何维护呢?观察access的时候,对于每个被改变颜色的点,都会发生实变虚、虚变实的过程。那么,对于这个点发生改变后的实儿子,其对应的子树中的所有点到根节点经过的颜色种类都要减一;而虚儿子对应的要加一。但是,由于LCT改变了原树的结构,我们不能够用LCT直接维护子树信息,而是要单独用一棵线段树来进行修改与查询。每次LCT时,分别找到他的实儿子和虚儿子在原树中对应的子树根节点,然后利用dfs序进行修改即可。

    对于操作3,在线段树中区间查询即可。

    代码

    #include <iostream>
    #include <cstdio>
    #define N 100002
    using namespace std;
    struct SegmentTree{
       int dat,add;
    }t[N*4];
    int head[N],ver[N*2],nxt[N*2],l;
    int n,m,i,son[N][2],fa[N],in[N],out[N],dep[N],pos[N],f[N][21],cnt;
    int read()
    {
       char c=getchar();
       int w=0;
       while(c<'0'||c>'9') c=getchar();
       while(c<='9'&&c>='0'){
       	w=w*10+c-'0';
       	c=getchar();
       }
       return w;
    }
    void insert(int x,int y)
    {
       l++;
       ver[l]=y;
       nxt[l]=head[x];
       head[x]=l;
    }
    void dfs(int x,int pre)
    {
       in[x]=++cnt;pos[cnt]=x;
       f[x][0]=pre;
       dep[x]=dep[pre]+1;
       for(int i=head[x];i;i=nxt[i]){
       	int y=ver[i];
       	if(y!=pre) dfs(y,x);
       }
       out[x]=cnt;
    }
    void update(int p)
    {
       t[p].dat=max(t[p*2].dat,t[p*2+1].dat);
    }
    void spread(int p)
    {
       if(t[p].add){
       	t[p*2].dat+=t[p].add;t[p*2].add+=t[p].add;
       	t[p*2+1].dat+=t[p].add;t[p*2+1].add+=t[p].add;
       	t[p].add=0;
       }
    }
    void build(int p,int l,int r)
    {
       if(l==r){
       	t[p].dat=dep[pos[l]];
       	return;
       }
       int mid=(l+r)/2;
       build(p*2,l,mid);
       build(p*2+1,mid+1,r);
       update(p);
    }
    void change(int p,int l,int r,int ql,int qr,int x)
    {
       if(ql<=l&&r<=qr){
       	t[p].dat+=x;t[p].add+=x;
       	return;
       }
       int mid=(l+r)/2;
       spread(p);
       if(ql<=mid) change(p*2,l,mid,ql,qr,x);
       if(qr>mid) change(p*2+1,mid+1,r,ql,qr,x);
       update(p);
    }
    int ask1(int p,int l,int r,int x)
    {
       if(l==r) return t[p].dat;
       int mid=(l+r)/2;
       spread(p);
       if(x<=mid) return ask1(p*2,l,mid,x);
       else return ask1(p*2+1,mid+1,r,x);
    }
    int ask2(int p,int l,int r,int ql,int qr)
    {
       if(ql<=l&&r<=qr) return t[p].dat;
       int mid=(l+r)/2,ans=0;
       spread(p);
       if(ql<=mid) ans=max(ans,ask2(p*2,l,mid,ql,qr));
       if(qr>mid) ans=max(ans,ask2(p*2+1,mid+1,r,ql,qr));
       return ans;
    }
    void init()
    {
       dfs(1,0);
       for(int i=1;i<=n;i++) fa[i]=f[i][0];
       for(int j=0;j<=19;j++){
       	for(int i=1;i<=n;i++) f[i][j+1]=f[f[i][j]][j];
       }
       build(1,1,n);
    }
    int LCA(int u,int v)
    {
       if(dep[u]>dep[v]) swap(u,v);
       int tmp=dep[v]-dep[u];
       for(int i=0;(1<<i)<=tmp;i++){
       	if((1<<i)&tmp) v=f[v][i];
       }
       if(u==v) return u;
       for(int i=19;i>=0;i--){
       	if(f[u][i]!=f[v][i]) u=f[u][i],v=f[v][i];
       }
       return f[u][0];
    }
    bool unroot(int x)
    {
       return son[fa[x]][0]==x||son[fa[x]][1]==x;
    }
    void rotate(int x)
    {
       int y=fa[x],z=fa[y],p=(son[y][1]==x),w=son[x][p^1];
       if(unroot(y)) son[z][son[z][1]==y]=x;
       son[x][p^1]=y;son[y][p]=w;
       if(w) fa[w]=y;
       fa[y]=x;fa[x]=z;
    }
    void splay(int x)
    {
       int y=x,z;
       while(unroot(x)){
       	y=fa[x];z=fa[y];
       	if(unroot(y)) rotate((son[z][1]==y)^(son[y][1]==x)?x:y);
       	rotate(x);
       }
    }
    int findroot(int x)
    {
       while(son[x][0]) x=son[x][0];
       return x;
    }
    void access(int x)
    {
       for(int y=0;x;y=x,x=fa[x]){
       	splay(x);
       	if(son[x][1]){
       		int p=findroot(son[x][1]);
       		change(1,1,n,in[p],out[p],1);
       	}
       	if(y){
       		int p=findroot(y);
       		change(1,1,n,in[p],out[p],-1);
       	}
       	son[x][1]=y;
       }
    }
    int main()
    {
       n=read();m=read();
       for(i=1;i<n;i++){
       	int u=read(),v=read();
       	insert(u,v);
       	insert(v,u);
       }
       init();
       for(i=1;i<=m;i++){
       	int op=read();
       	if(op==1){
       		int x=read();
       		access(x);
       	}
       	else if(op==2){
       		int x=read(),y=read(),lca=LCA(x,y);
       		printf("%d
    ",ask1(1,1,n,in[x])+ask1(1,1,n,in[y])-2*ask1(1,1,n,in[lca])+1);
       	}
       	else{
       		int x=read();
       		printf("%d
    ",ask2(1,1,n,in[x],out[x]));
       	}
       }
       return 0;
    }
    
  • 相关阅读:
    常见排序算法的实现
    Never-build package 'XXXX' requires always-build package 'EhLib70'
    自制的七个C,总结的太好了
    那些有影响力的语言
    数海笔记(0)-程序员为什么要学数学
    linux 多线程编程笔记
    双指数边缘平滑滤波器用于磨皮算法的尝试。
    对于树的序列化,用了stream,很好
    很聪明的解法
    按窗口获得最大数 和 中位数
  • 原文地址:https://www.cnblogs.com/LSlzf/p/13060307.html
Copyright © 2011-2022 走看看