BZOJ 1036: [ZJOI2008]树的统计Count

Description

一棵树上有n个节点,编号分别为1到n,每个节点都有一个权值w。我们将以下面的形式来要求你对这棵树完成一些操作: I. CHANGE u t : 把结点u的权值改为t II. QMAX u v: 询问从点u到点v的路径上的节点的最大权值 III. QSUM u v: 询问从点u到点v的路径上的节点的权值和 注意:从点u到点v的路径上的节点包括u和v本身

Input

输入的第一行为一个整数n,表示节点的个数。接下来n – 1行,每行2个整数a和b,表示节点a和节点b之间有一条边相连。接下来n行,每行一个整数,第i行的整数wi表示节点i的权值。接下来1行,为一个整数q,表示操作的总数。接下来q行,每行一个操作,以“CHANGE u t”或者“QMAX u v”或者“QSUM u v”的形式给出。 对于100%的数据,保证1<=n<=30000,0<=q<=200000;中途操作中保证每个节点的权值w在-30000到30000之间。

Output

对于每个“QMAX”或者“QSUM”的操作,每行输出一个整数表示要求输出的结果。

Sample Input

4
1 2
2 3
4 1
4 2 1 3
12
QMAX 3 4
QMAX 3 3
QMAX 3 2
QMAX 2 3
QSUM 3 4
QSUM 2 1
CHANGE 1 5
QMAX 3 4
CHANGE 3 6
QMAX 3 4
QMAX 2 4
QSUM 3 4

Sample Output

4
1
2
2
10
6
5
6
5
16

题解

树链剖分模板。3000多ms。

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<cmath>
using namespace std;
int n,zz,head[30002],v[30002];
struct bian {int to,nx;} e[60005];
int h[30002],fa[30002][16],son[30002];
bool vis[30002];
int size,bl[30002],tw[30002]; 
    //belong数组,记录每个点所在链的编号,tw数组表示点i在线段树上的位置*
struct shu {int l,r,sum,mx;} tr[120002];
void insert(int x,int y)
{
	zz++; e[zz].to=y; e[zz].nx=head[x]; head[x]=zz;
	zz++; e[zz].to=x; e[zz].nx=head[y]; head[y]=zz;
} 
void init()//输入1,处理树上的边 
{
	scanf("%d",&n);
	int x,y;
	for(int i=1;i<n;i++)
	   {scanf("%d%d",&x,&y); insert(x,y);}
	for(int i=1;i<=n;i++) scanf("%d",&v[i]);
}
void dfs1(int x)//处理深度h,和LCA的dfs相同,顺便再处理出每个子树的结点个数。 
{
	vis[x]=1; son[x]=1;
	for(int i=1;i<=14;i++)
	   {if(h[x]<(1<<i)) break;
	    fa[x][i]=fa[fa[x][i-1]][i-1];
	   }
	for(int i=head[x];i;i=e[i].nx)
	   {if(vis[e[i].to]) continue;
	    h[e[i].to]=h[x]+1; fa[e[i].to][0]=x;
	    dfs1(e[i].to);
	    son[x]+=son[e[i].to];
	   }
}
void dfs2(int x,int ll/*表示所在链的编号*/)//处理链 
{
	int k=0; size++;
	tw[x]=size; bl[x]=ll;
	for(int i=head[x];i;i=e[i].nx)
	   {if(h[e[i].to]>h[x]&&son[e[i].to]>son[k]) k=e[i].to;}
	if(k==0) return;
	dfs2(k,ll);
	for(int i=head[x];i;i=e[i].nx)
	   {if(h[e[i].to]>h[x]&&e[i].to!=k) dfs2(e[i].to,e[i].to);}
}
void build(int w,int l,int r)//线段树建树 
{
	tr[w].l=l; tr[w].r=r;
	if(l==r) return;
	int mid=(l+r)>>1;
	build(w<<1,l,mid); build((w<<1)+1,mid+1,r);
}
void change(int w,int x,int y)//权值修改(单点) 
{
	if(tr[w].l==tr[w].r) {tr[w].mx=tr[w].sum=y; return;}
	int mid=(tr[w].l+tr[w].r)>>1;
	if(x<=mid) change(w<<1,x,y);
	else change((w<<1)+1,x,y);
	tr[w].sum=tr[w<<1].sum+tr[(w<<1)+1].sum;
	tr[w].mx=max(tr[w<<1].mx,tr[(w<<1)+1].mx);
}
int lca(int x,int y)
{
	if(h[x]<h[y]) swap(x,y);
	int t=h[x]-h[y];
	for(int i=0;i<=14;i++)
	   {if(t&(1<<i)) x=fa[x][i];}
	for(int i=14;i>=0;i--)
	   {if(fa[x][i]!=fa[y][i])
	       {x=fa[x][i]; y=fa[y][i];}
	   }
	if(x==y) return x;
	else return fa[x][0];
}
int getm(int w,int l,int r)
{
	int x=tr[w].l,y=tr[w].r;
	if(l==x&&r==y) return tr[w].mx;
	int mid=(x+y)>>1;
	if(r<=mid) return getm(w<<1,l,r);
	else if(l>mid) return getm((w<<1)+1,l,r);
	else return max(getm(w<<1,l,mid),getm((w<<1)+1,mid+1,r));
}
int fmax(int x,int y)
{
	int big=-0x7fffffff;
	while(bl[x]!=bl[y])
	   {big=max(big,getm(1,tw[bl[x]],tw[x]));
	    x=fa[bl[x]][0];
	   }
	big=max(big,getm(1,tw[y],tw[x]));
	return big;
}
int gets(int w,int l,int r)
{
    int x=tr[w].l,y=tr[w].r;
    if(l==x&&y==r)return tr[w].sum;
    int mid=(x+y)>>1;
    if(r<=mid)return gets(w<<1,l,r);
    else if(l>mid)return gets((w<<1)+1,l,r);
    else return gets(w<<1,l,mid)+gets((w<<1)+1,mid+1,r);
}
int fsum(int x,int y)
{
    int sum=0;
    while(bl[x]!=bl[y])
       {sum+=gets(1,tw[bl[x]],tw[x]);
	    x=fa[bl[x]][0];
       }
    sum+=gets(1,tw[y],tw[x]);
    return sum;
}
void work()
{
	build(1,1,n);
	for(int i=1;i<=n;i++) change(1,tw[i],v[i]);
	int k,x,y; char ch[7];
	scanf("%d",&k);
	for(int i=1;i<=k;i++)
	   {scanf("%s%d%d",ch,&x,&y);
	    if(ch[0]=='C') {v[x]=y; change(1,tw[x],y);}
	    else
	       {int t=lca(x,y); //cout<<t<<" ";
		    if(ch[1]=='M') printf("%d\n",max(fmax(x,t),fmax(y,t)));
		    else printf("%d\n",fsum(x,t)+fsum(y,t)-v[t]);
		   }
	   }
}
int main()
{
	init(); dfs1(1); dfs2(1,1); work();
	return 0;
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值