HYSBZ - 2243染色——树链剖分+线段树建树技巧

【题目描述】
HYSBZ - 2243染色
在这里插入图片描述
【题目分析】
我一直没有看清楚题,以为求的是路径上出现颜色的种类,然后就写了一个区间染色的线段树进行维护,过样例的时候才发现题读错了,人家要求的是路径上出现的颜色段,所以颜色的种类不重要,重要的是每一段每一段。理所当然,我们应该用线段树维护所在区间有多少段。但是左右区间上传的时候如果边界颜色相同(左节点的右边界和右节点的左边界),那么区间个数应该减一。为此,我们还必须维护每个区间左边界和右边界分别是什么颜色以方便查询和上传。因为题目是区间修改,所以我们还要用lazy标记。(用lazy标记的时候要时时记得标记下传,就是因为单点查询的时候忘记要标记下传wa了一下午)
因为在树上,所以我们不仅仅需要判断线段树区间合并的时候左右端点颜色是否相同,还要判断每条链顶和链顶的父节点的颜色是否相同,如果相同答案减一。(每条链都在向链顶合并)

【AC代码】

#include<iostream>
#include<cstdio>
#include<vector>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<climits>

using namespace std;

const int MAXN=100005;	//时刻注意数据范围
vector<int>g[MAXN];
int fa[MAXN],A[MAXN],val[MAXN],color[MAXN],pos[MAXN];
bool check[MAXN];
int siz[MAXN],son[MAXN],h[MAXN],top[MAXN];
int cnt=0,n,m;
int num[MAXN<<2],lazy[MAXN<<2];
int lc[MAXN<<2],rc[MAXN<<2];


void dfs1(int u,int f)
{
    int i,v;
    siz[u]=1;
    son[u]=0;
    fa[u]=f;
    h[u]=h[f]+1;
    for(i=0;i<g[u].size();i++)
	{
        v=g[u][i];
        if(v!=f)
		{
            dfs1(v,u);
            siz[u]+=siz[v];
            if(siz[son[u]]<siz[v]) son[u]=v;
        }
    }
}
void dfs2(int u,int f,int k)
{
    int i,v;
    top[u]=k;
    pos[u]=++cnt;
    color[cnt]=val[u];
    if(son[u]) dfs2(son[u],u,k);
    for(i=0;i<g[u].size();i++)
	{
        v=g[u][i];
        if(v!=f&&v!=son[u]) dfs2(v,u,v);
    }
}

void pushup(int k)
{
	num[k]=num[k<<1]+num[k<<1|1];
	if(rc[k<<1]==lc[k<<1|1]) num[k]--;	//如果左右区间的连接处颜色相同则答案减一
	lc[k]=lc[k<<1]; rc[k]=rc[k<<1|1];
}

void build(int k,int l,int r)
{
	if(l==r)
	{
		num[k]=1;
		lc[k]=rc[k]=color[l];
		return;
	}
	int mid=(l+r)>>1;
	build(k<<1,l,mid);
	build(k<<1|1,mid+1,r);
	pushup(k);
}

void pushdown(int k)
{
	if(lazy[k])
	{
		lazy[k<<1]=lazy[k<<1|1]=lazy[k];
		lc[k<<1]=lc[k<<1|1]=lazy[k];
		rc[k<<1]=rc[k<<1|1]=lazy[k];
		num[k<<1]=num[k<<1|1]=1;
		lazy[k]=0;
	}
}

void ColorChange(int k,int l,int r,int L,int R,int v)
{
	if(l>=L && r<=R)
	{
		num[k]=1; lazy[k]=v;
		lc[k]=v; rc[k]=v;
		return;
	}
	int mid=(l+r)>>1;
	pushdown(k);
	if(L<=mid) ColorChange(k<<1,l,mid,L,R,v);
	if(R>mid) ColorChange(k<<1|1,mid+1,r,L,R,v);
	pushup(k); 
}

int QueryInterval(int k,int l,int r,int L,int R)
{
    if(L<=l && r<=R)
    {
    	return num[k];
	}
    int mid=(l+r)/2;
    pushdown(k);
    int ret=0;
    if(R<=mid) return QueryInterval(k<<1,l,mid,L,R);
    else if(L>mid) return QueryInterval(k<<1|1,mid+1,r,L,R);	//这里必须这样写,因为要考虑是否存在区间合并
    ret+=QueryInterval(k<<1,l,mid,L,R);
    ret+=QueryInterval(k<<1|1,mid+1,r,L,R);
    if(rc[k<<1]==lc[k<<1|1]) ret--;
    return ret;
}

int QueryPointColor(int k,int l,int r,int x)
{
	if(l==r && l==x)
	{
		return lc[k];
	}
	pushdown(k);	//因为这里忘记了.woc
	int mid=(l+r)>>1;
	if(x<=mid) return QueryPointColor(k<<1,l,mid,x);
	else return QueryPointColor(k<<1|1,mid+1,r,x);
}

int Findnum(int u,int v)
{
	memset(check,0,sizeof(check));
    int ans=0;
    while(top[u]!=top[v])
	{
		if(h[top[u]]<h[top[v]]) swap(u,v);
		ans+=QueryInterval(1,1,n,pos[top[u]],pos[u]);
		//printf("ans=%d\n",ans);
		//printf("QueryPointColor(1,1,n,pos[%d])=%d\nQueryPointColor(1,1,n,pos[%d])=%d\n",top[u],QueryPointColor(1,1,n,pos[top[u]]),fa[top[u]],QueryPointColor(1,1,n,pos[fa[top[u]]]));
		if(QueryPointColor(1,1,n,pos[top[u]]) == QueryPointColor(1,1,n,pos[fa[top[u]]]))	//考虑链与链的合并
		ans--;
		u=fa[top[u]];
    }
    if(h[u]<h[v]) swap(u,v);
    ans+=QueryInterval(1,1,n,pos[v],pos[u]);
    //printf("ans=%d\n",ans);
    return ans;
}

void update(int u,int v,int w)
{
    while(top[u]!=top[v])
	{
		if(h[top[u]]<h[top[v]]) swap(u,v);
		ColorChange(1,1,n,pos[top[u]],pos[u],w);
		u=fa[top[u]];
    }
    if(h[u]<h[v]) swap(u,v);
   	ColorChange(1,1,n,pos[v],pos[u],w);
}


int main()
{
    int a,b,c;
    char s[10];
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++) scanf("%d",&val[i]);
    for(int i=1;i<n;i++)
	{
        scanf("%d%d",&a,&b);
        g[a].push_back(b);
        g[b].push_back(a);
    }
    
    dfs1(1,0);
    dfs2(1,0,1);
    
	build(1,1,n);
	
    while(m--)
	{
        scanf("%s",s);
        if(s[0]=='C')
        {
        	scanf("%d%d%d",&a,&b,&c);
        	update(a,b,c);
		}
        else if(s[0]=='Q') 
        {
        	scanf("%d%d",&a,&b);
        	printf("%d\n",Findnum(a,b));
		}
    }
    
    return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值