[SDOI2011]染色

一、题目

点此看题

二、解法

这道题卡了一会,看过网上的题解,都没懂,讲一下我自己 y y yy yy的一种解法。

首先维护每个点的最左颜色 l c lc lc,最右颜色 r c rc rc,总颜色段数,和颜色覆盖标记,合并 A , B A,B A,B时就判断 A . r c = B . l c A.rc=B.lc A.rc=B.lc是否成立,成立就把总颜色段数减一。这个合并可以重载加法,懒标记的处理也不难,线段树基本操作就可以维护剖分树链。

最有意思的地方还是询问,要把询问颜色段数放在树上,那么详细讲一下:
在这里插入图片描述
我们先拼凑 u u u到相同 t o p top top的路径,由于询问返回的段是深度小-深度大,我们需要把询问返回的段给翻转(具体就是直接交换左右颜色),设 t t t是原来的, t ′ t' t是新的段,就用 t + t ′ t+t' t+t来拼接。对于 v v v,不需要翻转,直接用 t ′ + t t'+t t+t来拼接即可。

跑到一个 t o p top top后我们要考虑 u , v u,v u,v的相对位置,如果 u u u在上面(深度小),直接接到中间。如果 v v v在上面,翻转后拼接到中间即可。

#pragma GCC optimize(2)
#include <cstdio>
#include <cstring>
#include <iostream>
#define inf 0x3f3f3f3f
using namespace std;
const int M = 100005;
int read()
{
    int x=0,flag=1;char c;
    while((c=getchar())<'0' || c>'9') if(c=='-') flag=-1;
    while(c>='0' && c<='9') x=(x<<3)+(x<<1)+(c^48),c=getchar();
    return x*flag;
}//区间修改,颜色段数 
int n,m,tot,a[M],c[M],siz[M],f[M],fa[M];
int Index,son[M],dep[M],num[M],top[M]; 
char s[5];
struct edge
{
    int v,next;
    edge(int V=0,int N=0) : v(V) , next(N) {}
}e[2*M];
struct node
{
	int lc,rc,sum,la;
	node(int L=0,int R=0,int S=0,int La=0) : lc(L) , rc(R) , sum(S) , la(La) {}
	node operator + (const node &B) const {
		if(sum==0) return B;
		if(B.sum==0) return *this;
		return node(lc,B.rc,sum+B.sum-(rc==B.lc),0);
	}
	void reverse() {
		swap(lc,rc);
	}
}tr[4*M];
void dfs1(int u,int p)
{
    fa[u]=p;
    dep[u]=dep[p]+1;
    siz[u]=1;
    for(int i=f[u];i;i=e[i].next)
    {
        int v=e[i].v;
        if(v==p) continue;
        dfs1(v,u);
        siz[u]+=siz[v];
        if(siz[v]>siz[son[u]])
            son[u]=v;
    }
}
void dfs2(int u,int tp)
{
    top[u]=tp;
    num[u]=++Index;
    c[Index]=a[u];
    if(son[u]) dfs2(son[u],tp);
    for(int i=f[u];i;i=e[i].next)
        if(e[i].v^son[u] && e[i].v^fa[u])
            dfs2(e[i].v,e[i].v);
}
void build(int i,int l,int r)
{
    if(l==r)
    {
        tr[i]=node(c[l],c[l],1,0);
        return ;
    }
    int mid=(l+r)>>1;
    build(i<<1,l,mid);
    build(i<<1|1,mid+1,r);
    tr[i]=tr[i<<1]+tr[i<<1|1];
}
void change(int i,int c)
{
	tr[i]=node(c,c,1,c);
}
void down(int i)
{
	if(tr[i].la==0) return ;
	change(i<<1,tr[i].la);
	change(i<<1|1,tr[i].la);
	tr[i].la=0;
}
void updata(int i,int l,int r,int L,int R,int v)
{
    if(l>R || L>r) return ;
    if(L<=l && r<=R)
    {
        change(i,v);
        return ;
    }
    down(i);
    int mid=(l+r)>>1;
    updata(i<<1,l,mid,L,R,v);
    updata(i<<1|1,mid+1,r,L,R,v);
    tr[i]=tr[i<<1]+tr[i<<1|1];
}
node query(int i,int l,int r,int L,int R)
{
    if(l>R || L>r) return 0;
    if(L<=l && r<=R) return tr[i];
    down(i);
    int mid=(l+r)>>1;
	node t1=query(i<<1,l,mid,L,R);
    return t1+query(i<<1|1,mid+1,r,L,R);
}
void modify(int u,int v,int c)
{
    while(top[u]^top[v])
    {
        if(dep[top[u]]<=dep[top[v]]) swap(u,v);
        updata(1,1,n,num[top[u]],num[u],c);
        u=fa[top[u]];
    }
    if(dep[u]<=dep[v]) swap(u,v);
    updata(1,1,n,num[v],num[u],c);
}
int ask(int u,int v)
{
	node t1,t2;
	while(top[u]^top[v])
	{
		if(dep[top[u]]>=dep[top[v]])
		{
			node t=query(1,1,n,num[top[u]],num[u]);
			t.reverse();
			t1=t1+t;
			u=fa[top[u]];
		}
		else
		{
			t2=query(1,1,n,num[top[v]],num[v])+t2;
			v=fa[top[v]];
		}
	}
	if(dep[u]>=dep[v])
	{
		node t=query(1,1,n,num[v],num[u]);
		t.reverse();
		t1=t1+t+t2;
		return t1.sum;
	}
	t1=t1+query(1,1,n,num[u],num[v])+t2;
	return t1.sum;
}
int main()
{
    n=read();m=read();
    for(int i=1;i<=n;i++)
    	a[i]=read();
    for(int i=1;i<n;i++)
    {
    	int u=read(),v=read();
    	e[++tot]=edge(v,f[u]),f[u]=tot;
    	e[++tot]=edge(u,f[v]),f[v]=tot;
	}
	dfs1(1,0);
	dfs2(1,1);
	build(1,1,n);
	while(m--)
	{
		scanf("%s",s);
		int u=read(),v=read();
		if(s[0]=='C')
			modify(u,v,read());
		else
			printf("%d\n",ask(u,v));
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值