[SDOI2014]旅行

[SDOI2014]旅行https://www.luogu.org/problemnew/show/P3313

题目描述

\(S\)国有\(N\)个城市,编号从\(1\)\(N\)。城市间用\(N-1\)条双向道路连接,满足从一个城市出发可以到达其它所有城市。每个城市信仰不同的宗教,如飞天面条神教、隐形独角兽教、绝地教都是常见的信仰。
为了方便,我们用不同的正整数代表各种宗教, \(S\)国的居民常常旅行。旅行时他们总会走最短路,并且为了避免麻烦,只在信仰和他们相同的城市留宿。当然旅程的终点也是信仰与他相同的城市。S国政府为每个城市标定了不同的旅行评级,旅行者们常会记下途中(包括起点和终点)留宿过的城市的评级总和或最大值。
在S国的历史上常会发生以下几种事件:
\(CC\) \(x\) \(c\):城市x的居民全体改信了\(c\)教;
\(CW\) \(x\) \(w\):城市x的评级调整为\(w\);
\(QS\) \(x\) \(y\):一位旅行者从城市\(x\)出发,到城市\(y\),并记下了途中留宿过的城市的评级总和;
\(QM\) \(x\) \(y\):一位旅行者从城市\(x\)出发,到城市\(y\),并记下了途中留宿过的城市的评级最大值。
由于年代久远,旅行者记下的数字已经遗失了,但记录开始之前每座城市的信仰与评级,还有事件记录本身是完好的。请根据这些信息,还原旅行者记下的数字。 为了方便,我们认为事件之间的间隔足够长,以致在任意一次旅行中,所有城市的评级和信仰保持不变。

输入格式:

输入的第一行包含整数\(N,Q\)依次表示城市数和事件数。 接下来\(N\)行,第\(i+1\)行两个整数\(W_i\)\(C_i\)依次表示记录开始之前,城市\(i\)的评级和信仰。 接下来\(N-1\)行每行两个整数\(x,y\)表示一条双向道路。 接下来\(Q\)行,每行一个操作,格式如上所述。

输出格式:

对每个\(QS\)\(QM\)事件,输出一行,表示旅行者记下的数字。

输入样例:

5 6
3 1
2 3
1 2
3 3
5 1
1 2
1 3
3 4
3 5
QS 1 5
CC 3 1
QS 1 5
CW 3 3
QS 1 5
QM 2 4

输出样例:

8
9
11
3

说明

\(N\)\(Q < =10^5\)\(C < =10^5\)
数据保证对所有\(QS\)\(QM\)事件,起点和终点城市的信仰相同;在任意时刻,城市的评级总是不大于\(10^4\)的正整数,且宗教值不大于\(C\)


树剖
此题第一想法对每种宗教维护一个线段树
但空间肯定开不下,而且浪费了许多空间
于是...
看题解,问大佬,发现了一种神奇的操作...
"动态"线段树
对于一个点的修改,只会影响到\(logn\)个节点,所以一开始一个空线段树,动态的添加点,对每个点维护根节点,左右儿子(不再是\(root<<1\)\(root<<1|1\)了),总空间\([(N+Q)*logN]\)

#define RG register
#include<cstdio>
#include<iostream>
using namespace std;
const int N=1e5+5;
inline int read()
{
	RG int x=0,w=1;RG char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
	while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();
	return x*w;
}
int n,m,cnt,ct,tot,Q;
int last[N],w[N],c[N],dfn[N],size[N],son[N],fa[N],dep[N],top[N],rt[N];//rt[]每个宗教对应线段树的根节点
int sum[N*40],Max[N*40],ls[N*40],rs[N*40];
struct edge{int to,next;}e[N<<1];
inline void insert(int u,int v)
{
	e[++cnt]=(edge){v,last[u]};last[u]=cnt;
	e[++cnt]=(edge){u,last[v]};last[v]=cnt;
}
void dfs1(int now)
{
    size[now]=1;
    for(int i=last[now];i;i=e[i].next)
    {
        int v=e[i].to;
        if(v==fa[now])continue;
        fa[v]=now;
        dep[v]=dep[now]+1;
        dfs1(v);
        size[now]+=size[v];
        if(size[v]>size[son[now]])son[now]=v;
    }
}
void dfs2(int now,int Top)
{
    top[now]=Top;dfn[now]=++ct;
    if(son[now])dfs2(son[now],Top);
    for(int i=last[now];i;i=e[i].next)
    {
        int v=e[i].to;
        if(v==fa[now]||v==son[now])continue;
        dfs2(v,v);
    }
}
void Modify(int &root,int l,int r,int s,int k)//注意&,这个操作动态维护了左右儿子和根节点
{
	if(!root)root=++tot;
	if(l==r){sum[root]=Max[root]=k;return;}
	int mid=(l+r)>>1;
	if(s<=mid)Modify(ls[root],l,mid,s,k);
	else Modify(rs[root],mid+1,r,s,k);
	sum[root]=sum[ls[root]]+sum[rs[root]];
    Max[root]=max(Max[ls[root]],Max[rs[root]]);
}
int Query(int root,int l,int r,int ll,int rr,int op)
{
	if(ll<=l&&r<=rr)
	{
		if(op)return sum[root];
		return Max[root];
	}
	int mid=(l+r)>>1,Ans=0;
	if(ll<=mid)
	{
		if(op)Ans+=Query(ls[root],l,mid,ll,rr,op);
		else Ans=max(Ans,Query(ls[root],l,mid,ll,rr,op));
	}
	if(mid<rr)
	{
		if(op)Ans+=Query(rs[root],mid+1,r,ll,rr,op);
		else Ans=max(Ans,Query(rs[root],mid+1,r,ll,rr,op));
	}
	return Ans;
}
inline int Query_Tree(int x,int y,int op)
{
	int Ans=0,k=rt[c[x]];//k表示根节点
	while(top[x]!=top[y])
	{
		if(dep[top[x]]<dep[top[y]])swap(x,y);
		if(op)Ans+=Query(k,1,n,dfn[top[x]],dfn[x],op);
		else Ans=max(Ans,Query(k,1,n,dfn[top[x]],dfn[x],op));
		x=fa[top[x]];
	}
	if(dep[x]>dep[y])swap(x,y);
	if(op)Ans+=Query(k,1,n,dfn[x],dfn[y],op);
	else Ans=max(Ans,Query(k,1,n,dfn[x],dfn[y],op));
	return Ans;
}
int main()
{
	n=read();
	Q=read();
	for(int i=1;i<=n;i++)w[i]=read(),c[i]=read();
	for(int i=1;i<n;i++)
	{
		RG int a=read(),b=read();
		insert(a,b);
	}
	dep[1]=1;
	dfs1(1);
	dfs2(1,1);
	for(int i=1;i<=n;i++)Modify(rt[c[i]],1,n,dfn[i],w[i]);//初始化线段树
	RG char act[5];
	RG int x,y;
	while(Q--)
	{
		scanf("%s",act);
		x=read();y=read();
		if(act[1]=='C')
		{
			Modify(rt[c[x]],1,n,dfn[x],0);//将原来节点变成0
			Modify(rt[y],1,n,dfn[x],w[x]);//将宗教y所在线段树中新建节点
			c[x]=y;//修改宗教
		}
		if(act[1]=='W')Modify(rt[c[x]],1,n,dfn[x],y),w[x]=y;//修改权值
		if(act[1]=='S')printf("%d\n",Query_Tree(x,y,1));//op==1查询SUM
		if(act[1]=='M')printf("%d\n",Query_Tree(x,y,0));//op==0查询MAX
	}
	return 0;
}
posted @ 2018-02-26 22:30  sdzwyq  阅读(218)  评论(0编辑  收藏  举报