记众多数据结构的组合题——SDOI2017树点涂色

题目描述

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

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

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

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

2 x y
求xx 到yy 的路径的权值。

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

Bob一共会进行mm 次操作

输入输出格式

输入格式:
第一行两个数n,mn,m 。

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

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

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

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

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

输入输出样例

输入样例#1:
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
输出样例#1:
3
4
2
2
说明
n<=1e5,m<=1e5 n <= 1 e 5 , m <= 1 e 5

时间限制:1s

空间限制:128MB


看到这道题,咦?access?
仔细思考一波,你发现,他可以维护一个当前节点到跟节点的颜色种数,然后利用 ans=col(x)+col(y)2col(lca(x,y))+1 a n s = c o l ( x ) + c o l ( y ) − 2 c o l ( l c a ( x , y ) ) + 1 求解,然后你可以利用access和线段树来完成它,但是会有许多坑的地方——

  • 线段树不要写错(比如说把L<=l写成L<=r)
  • 每次修改的子树是原树的子树而不是splay的!
  • depu d e p u >= depv d e p v 不要写成 >
    下面用代码说明一波:
#include<bits/stdc++.h>
using namespace std;
#define MN 100010
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1 
//fa1 为 袁术  fa2 为 LCT
struct lll{
    int to,ne;
}a[MN*2];
int n,q,x,y,lk[MN],h[MN],up[MN][40],fa2[MN],ch[MN][2],zs[MN],vis[MN],dep[MN],tag[MN*20],
dfn[MN],tot,cnt,opt,mm[MN*20];
void link(int x,int y)
{
    a[++cnt].to=y,a[cnt].ne=h[x],h[x]=cnt;
}
 void dfs(int u)
 {
    lk[u]=++tot,dfn[tot]=u;
    for(int i=1;i<=29;i++) up[u][i]=up[up[u][i-1]][i-1];
    for(int i=h[u];i;i=a[i].ne)
    {
        int v=a[i].to;
        if(!vis[v])
        {
            up[v][0]=u;dep[v]=dep[u]+1;fa2[v]=u;vis[v]=1;dfs(v);
        }
    }
    zs[u]=tot;
 }
 int lca(int u,int v)
 {
    if(dep[u]<dep[v]) swap(u,v);
    for(int i=29;i>=0;i--)
    if(dep[up[u][i]]>=dep[v]) u=up[u][i];// wrong 1st 这里写成了 dep[up[u][i]]>dep[v]
    if(u==v) return u;
    for(int i=29;i>=0;i--)
    if(up[u][i]!=up[v][i]) u=up[u][i],v=up[v][i];
    return up[u][0];
 }
 void pushup(int rt)
 {
    mm[rt]=max(mm[rt<<1],mm[rt<<1|1]);
 }
 void build(int l,int r,int rt)
 {
    if(l==r) {mm[rt]=dep[dfn[l]];return ;}
    int mid=(r+l)>>1;
    build(lson);build(rson);pushup(rt);
 }
 void pushdown(int rt)
 {
    if(tag[rt])
    {
        mm[rt<<1]+=tag[rt],tag[rt<<1]+=tag[rt];
        mm[rt<<1|1]+=tag[rt],tag[rt<<1|1]+=tag[rt];
        tag[rt]=0;
    }
    pushup(rt);
 }
 void update(int L,int R,int l,int r,int rt,int c)
 {
    if(L<=l&&r<=R) {mm[rt]+=c;tag[rt]+=c;return ;}
    int mid=(r+l)>>1;pushdown(rt);
    if(L<=mid) update(L,R,lson,c);
    if(mid<R) update(L,R,rson,c);
    pushup(rt);
 }
 int ask(int pos,int l,int r,int rt)
 {

    if(l==r) {return mm[rt];}
    int mid=(r+l)>>1;pushdown(rt);
    if(pos<=mid) return ask(pos,lson);
    else  return ask(pos,rson);

 }
 int maxx(int L,int R,int l,int r,int rt)
 {
    int ans=-2100000000;
    if(L<=l&&r<=R) return mm[rt];// wrong 2ed 错写为了 L<=r&&r<=R
    int mid=(r+l)>>1;pushdown(rt);
    if(L<=mid) ans=max(ans,maxx(L,R,lson));
    if(mid<R) ans=max(ans,maxx(L,R,rson));
    return ans;
 }
 bool nroot(int x)
 {
    return ch[fa2[x]][1]==x||ch[fa2[x]][0]==x;
 }
 void rotate(int x)
 {
    int old=fa2[x],oldf=fa2[old],which=(ch[old][1]==x),w=ch[x][!which];
    if(nroot(old)) ch[oldf][ch[oldf][1]==old]=x;
    ch[x][!which]=old;ch[old][which]=w;
    if(w) fa2[w]=old;
    fa2[x]=oldf,fa2[old]=x;
 }
 void splay(int x)
 {
    while(nroot(x))
    {
        if(nroot(fa2[x]))
        rotate(((ch[fa2[x]][1]==x)^(ch[fa2[fa2[x]]][1]==fa2[x]))?x:fa2[x]);
        rotate(x);
    }
 }
 int frt(int x)
 {
    while(ch[x][0]) x=ch[x][0];
    return x;
 }
 void access(int x)
 {
    for(int y=0;x;x=fa2[y=x])// wrong 3ed 这里很关键,你修改的是原树的子树所以要findroot一下! 
    {
        int w;
        splay(x);if(ch[x][1]) w=frt(ch[x][1]) ,update(lk[w],zs[w],1,n,1,1);
        ch[x][1]=y;if(y) w=frt(y) ,update(lk[w],zs[w],1,n,1,-1);
    }
 }
 int gsum(int x)
 {
    int ans;
    ans=ask(x,1,n,1);
    return ans;
 }
 void Prt(int l,int r,int rt)
 {
    if(l==r) {printf("%d",mm[rt]);return ;}
    int mid=(l+r)>>1;pushdown(rt);
    Prt(lson);Prt(rson);
 } 
int main()
{
    scanf("%d%d",&n,&q);
    for(int i=1;i<n;i++) scanf("%d%d",&x,&y),link(x,y),link(y,x);
    vis[1]=dep[1]=1;dfs(1);build(1,n,1); 
    //Prt(1,n,1);
    //for(int i=1;i<=n;i++) printf("%d ",dfn[i]);
    //printf("%d",gsum(lk[3]));
    while(q--)
    {
        scanf("%d" ,&opt);
        if(opt==1) scanf("%d",&x),access(x);
        if(opt==2) scanf("%d%d",&x,&y),
        printf("%d\n",(gsum(lk[y])+gsum(lk[x])-2*gsum(lk[lca(x,y)])+1));
        if(opt==3) scanf("%d",&x),printf("%d\n",maxx(lk[x],zs[x],1,n,1));
        //Prt(1,n,1);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值