题解 P2486 【[SDOI2011]染色】

yyb_test

2018-04-11 21:40:05

题解

[SDOI2011]染色

这道题有两种解法,树剖&LCT 但是实现方法却截然不同 做完本题后对树剖和LCT会有更深的认识 让我们充分认识到两者之间的优缺点

题解 1 -- 树链剖分

优点:暴力,直接

缺点:代码量大,常数大(毕竟是优雅的暴力)

做法:

一、线段树:

需要多维护两个值,lc和rc,分别记录右端 点和左端点的颜色。

在合并两个子区间的时候,如果左区间的右端点颜色=右区间的左端点颜色,则颜色带数量-1

二、路径求和

当然不能naive地把路径上的颜色带累加

我们发现树剖求LCA是利用两个点按照深度向上跳最后跳到一起的方法

自下而上,根据深度交替向上跳

而这个路径可以看成‘人’字型

我们不妨把这个路径分成两边

左边和右边

我们再用变量

pos1表示当前要往上跳的路径\上次的终点颜色

pos2表示另一条路径\上一次的终点颜色

这样,如果当前往上跳的路径\这次的起点颜色等于 当前要往上跳的路径\上次的终点颜色 那么颜色段数量-1

如果当前要往上跳的节点所在路径发生了改变(也就是路径发生了交替),则swap(pos1,pos2)

然后问题就落到如何找起点终点颜色了

很简单,起点颜色就是线段树上查询的左端点的颜色,终点颜色就是查询的右端点的颜色。

在往上跳(线段树查询)的时候顺便记录一下Lc和Rc即可

#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <vector>
#include <queue>

#define rg register int
#define ll long long
#define RG register 
#define il inline

using namespace std;

il int gi() {
    rg x=0,o=0;RG char ch=getchar();
    while(ch!='-'&&(ch<'0'||'9'<ch)) ch=getchar();
    if(ch=='-') o=1,ch=getchar();
    while('0'<=ch&&ch<='9') x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
    return o?-x:x;
}

#define SZ 1000002

int n,m,pos1,pos2,lc,rc,col[SZ];
char ch;

struct Edge { int to,nxt; }e[SZ<<1];
int Ehead[SZ],Ecnt=2;
il void Eadd(rg u,rg v) {
    e[Ecnt]=(Edge){v,Ehead[u]};
    Ehead[u]=Ecnt++;
    e[Ecnt]=(Edge){u,Ehead[v]};
    Ehead[v]=Ecnt++;
}

int cnt,fa[SZ],id[SZ],rid[SZ],siz[SZ],dep[SZ],son[SZ],top[SZ];
il void pou_debug() {
    for(rg i=1; i<=n; ++i) 
        printf("fa:%d id:%d son:%d siz:%d dep:%d top:%d\n",fa[i],id[i],son[i],siz[i],dep[i],top[i]);

}
void dfs1(rg u,rg ff) {
    fa[u]=ff,dep[u]=dep[ff]+1,siz[u]=1;
    for(rg v,i=Ehead[u]; i; i=e[i].nxt) {
        v=e[i].to;
        if(v==ff) continue;
        dfs1(v,u);
        siz[u]+=siz[v];
        if(siz[v]>siz[son[u]]) son[u]=v;
    }
}   
void dfs2(rg u,rg tp) {
    top[u]=tp,id[u]=++cnt,rid[cnt]=u;
    if(!son[u]) return;
    dfs2(son[u],tp);
    for(rg v,i=Ehead[u]; i; i=e[i].nxt) {
        v=e[i].to;
        if(v==son[u]||v==fa[u]) continue;
        dfs2(v,v);
    }
} 

#define lson rt<<1
#define rson rt<<1|1
struct Segtree { int l,r,lc,rc,c,v;  }tr[SZ<<4];
il void Seg_debug() {
    for(rg i=1; i<=n*3; ++i) 
        printf("#%d : l:%d r:%d lc:%d rc:%d c:%d v:%d\n",i,tr[i].l,tr[i].r,tr[i].lc,tr[i].rc,tr[i].c,tr[i].v);
}
il void pushup(rg rt) {
    tr[rt].v=tr[lson].v+tr[rson].v;
    if(tr[lson].rc==tr[rson].lc) --tr[rt].v;
    tr[rt].lc=tr[lson].lc;
    tr[rt].rc=tr[rson].rc;
}
il void pushcol(rg rt,rg col) {
    tr[rt].lc=tr[rt].rc=col;
    tr[rt].v=1,tr[rt].c=col;
}
il void pushdown(rg rt) {
    if(tr[rt].c) {
        if(lson) pushcol(lson,tr[rt].c);
        if(rson) pushcol(rson,tr[rt].c);
        tr[rt].c=0;
    }
}
void build(rg rt,rg l,rg r) {
    tr[rt].l=l,tr[rt].r=r;
    if(l==r) {
        tr[rt].lc=tr[rt].rc=col[rid[l]];
        tr[rt].v=1;
        return;
    }
    rg mid=l+r>>1;
    build(lson,l,mid);
    build(rson,mid+1,r);
    pushup(rt);
}
void modify(rg rt,rg L,rg R,rg x) {
    rg l=tr[rt].l,r=tr[rt].r;
    if(L<=l&&r<=R) {
        pushcol(rt,x);
        return;
    }
    pushdown(rt);
    rg mid=l+r>>1;
    if(L<=mid) modify(lson,L,R,x);
    if(R>mid) modify(rson,L,R,x);
    pushup(rt);
}
int query(rg rt,rg L,rg R) {
    rg l=tr[rt].l,r=tr[rt].r;   
    if(L<=l&&r<=R) {
        if(l==L) lc=tr[rt].lc;
        if(r==R) rc=tr[rt].rc;      
        return tr[rt].v;  
    }
    pushdown(rt);
    rg mid=l+r>>1;
    if(R<=mid) return query(lson,L,R);
    if(L>mid)  return query(rson,L,R);
    rg ret=query(lson,L,R)+query(rson,L,R);
    if(tr[lson].rc==tr[rson].lc) --ret;
    return ret; 
}
il void add(rg u,rg v,rg c) {
    while(top[u]!=top[v]) {
        if(dep[top[u]]<dep[top[v]]) swap(u,v);
        modify(1,id[top[u]],id[u],c);
        u=fa[top[u]];
    }
    if(id[u]>id[v]) swap(u,v);
    modify(1,id[u],id[v],c);
}
il int ask(rg u,rg v) {
    rg ret=0;
    pos1=pos2=0;
    while(top[u]!=top[v]) {
        if(dep[top[u]]<dep[top[v]]) swap(u,v),swap(pos1,pos2);      
        ret+=query(1,id[top[u]],id[u]);
        if(rc==pos1) --ret;
        pos1=lc,u=fa[top[u]];
    }
    if(id[u]>id[v]) swap(u,v),swap(pos1,pos2);
    ret+=query(1,id[u],id[v]);
    if(lc==pos1) --ret;
    if(rc==pos2) --ret; 
    return ret;
}

int main() {
    n=gi(),m=gi();
    for(rg i=1; i<=n; ++i) col[i]=gi();
    for(rg u,v,i=1; i<n; ++i) 
        u=gi(),v=gi(),Eadd(u,v);
    dfs1(1,0);
    dfs2(1,1);
    build(1,1,n);
    for(rg a,b,c,i=1; i<=m; ++i) {
        ch=getchar();
        while(ch!='C'&&ch!='Q') ch=getchar();
        if(ch=='C') {
            a=gi(),b=gi(),c=gi();
            add(a,b,c);
        }
        if(ch=='Q') {
            a=gi(),b=gi();
            printf("%d\n",ask(a,b));
        }
    }
    return 0;
}

题解2--LCT

优点:常数小,代码短

缺点:难想,难实现,细节多

做法:

我们考虑把连接不同色点的边权值设为1,连接同色的点的边权设为0,这样我们就可以把问题转化为查询这条路径上所有的边权和,你要输出的就是这个答案加一

对于维护,我们对每个splay节点一个最左端点的值和最右端点的颜色,这样,对于它的父亲节点,就可以很轻松地找到其前驱与后继的颜色,这样便可以累计它跟前驱后继所连边的权值和了。

前驱=左儿子最右端点,后继=右儿子最左端点

注意:区间反转时最左和最右端点也要反转

#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <vector>
#include <queue>

#define rg register int
#define ll long long
#define RG register 
#define il inline

using namespace std;

il int gi() {
    rg x=0,o=0;RG char ch=getchar();
    while(ch!='-'&&(ch<'0'||'9'<ch)) ch=getchar();
    if(ch=='-') o=1,ch=getchar();
    while('0'<=ch&&ch<='9') x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
    return o?-x:x;
}
#define SZ 1000010
int n,m;
char ch;

#define lson tr[x].ch[0]
#define rson tr[x].ch[1]
struct Splaytree { int w,c,lc,rc,tag,rev,fa,ch[2]; }tr[SZ];
int stk[SZ],top;

il bool isroot(rg x) { return tr[tr[x].fa].ch[0]!=x&&tr[tr[x].fa].ch[1]!=x; }

il void pushdown(rg x) {
    if(tr[x].rev) {
        swap(lson,rson);
        swap(tr[lson].lc,tr[lson].rc);
        swap(tr[rson].lc,tr[rson].rc);
        tr[lson].rev^=1,tr[rson].rev^=1;
        tr[x].rev=0;
    }
    if(tr[x].tag) {
        tr[x].lc=tr[x].rc=tr[x].c=tr[x].tag;
        tr[lson].tag=tr[rson].tag=tr[x].tag;
        tr[x].w=tr[x].tag=0;
    }
}

il void pushup(rg x) {
    pushdown(lson),pushdown(rson);
    tr[x].w=tr[lson].w+tr[rson].w;
    if(lson) {
        tr[x].lc=tr[lson].lc;
        if(tr[x].c!=tr[lson].rc) ++tr[x].w;
    }
    else tr[x].lc=tr[x].c;
    if(rson) {
        tr[x].rc=tr[rson].rc;
        if(tr[x].c!=tr[rson].lc) ++tr[x].w;
    }
    else tr[x].rc=tr[x].c;
}

il void rotate(rg x) {
    rg y=tr[x].fa,z=tr[y].fa;
    rg k=tr[y].ch[1]==x;
    if(!isroot(y)) tr[z].ch[tr[z].ch[1]==y]=x;  tr[x].fa=z;
    tr[y].ch[k]=tr[x].ch[k^1],tr[tr[x].ch[k^1]].fa=y;
    tr[x].ch[k^1]=y,tr[y].fa=x;
    pushup(y);
}

il void splay(rg x) {
    stk[top=1]=x;
    for(rg i=x; !isroot(i); i=tr[i].fa) stk[++top]=tr[i].fa;
    while(top) pushdown(stk[top--]);
    while(!isroot(x)) {
        rg y=tr[x].fa,z=tr[y].fa;
        if(!isroot(y))
            (tr[y].ch[0]==x)^(tr[z].ch[0]==y)?rotate(x):rotate(y);
        rotate(x);  
    }
    pushup(x);
}

il void access(rg x) {
    for(rg y=0; x; y=x,x=tr[x].fa) splay(x),rson=y,pushup(x);
}

il void makeroot(rg x) {
    access(x),splay(x),tr[x].rev^=1;
}

il int findroot(rg x) {
    access(x),splay(x);
    while(lson) x=lson;
    return x;
}

il void split(rg x,rg y) {
    makeroot(x),access(y),splay(y);
}

il void link(rg x,rg y) {
    makeroot(x),tr[x].fa=y;
}

int main() {
    n=gi(),m=gi();
    for(rg i=1; i<=n; ++i) tr[i].c=tr[i].lc=tr[i].rc=gi();
    for(rg u,v,i=1; i<n; ++i) u=gi(),v=gi(),link(u,v);
    for(rg a,b,c,i=1; i<=m; ++i) {
        ch=getchar();
        while(ch!='C'&&ch!='Q') ch=getchar();
        if(ch=='C') {
            a=gi(),b=gi(),c=gi();
            split(a,b);
            tr[b].tag=c;
        }
        if(ch=='Q') {
            a=gi(),b=gi();
            split(a,b);
            printf("%d\n",tr[b].w+1);
        }
    }
    return 0;
}

博客链接