Tree Description You are given a tree with N nodes. The tree’s nodes are numbered 1 through N and its edges are numbered 1 through N − 1. Each edge is associated with a weight. Then you are to execute a series of instructions on the tree. The instructions can be one of the following forms: Input The input contains multiple test cases. The first line of input contains an integer t (t ≤ 20), the number of test cases. Then follow the test cases. Each test case is preceded by an empty line. The first nonempty line of its contains N (N ≤ 10,000). The next N − 1 lines each contains three integers a, b and c, describing an edge connecting nodes a and b with weight c. The edges are numbered in the order they appear in the input. Below them are the instructions, each sticking to the specification above. A lines with the word “ Output For each “ Sample Input
Sample Output
Source POJ Monthly--2007.06.03, Lei, Tao |
Time Limit: 5000MS | | Memory Limit: 131072K |
Total Submissions: 11989 | | Accepted: 3088 |
[Submit] [Go Back] [Status] [Discuss]
【题意】
给一棵树,每条边有一个权值。有三种操作:1.修改某条边权值 2.将uv两点之间路径上的边权值全部取相反数 3. 查询uv两点之间路径上的最大值。
【分析】
树建图后,除根节点外,每条边的权值转移给对应儿子结点,然后就变成了点权的树链剖分。
1. 树链剖分一下,顺便记一个数组 bev[i] 表示第i条边转移给了哪个点。
2. 线段树维护最大值最小值,当进行区间取反操作时,取反并交换即可。
【代码】
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<iostream>
using namespace std;
typedef long long ll;
const int MAX=5e4+5;
const int INF=0x3f3f3f3f;
/*********** 图 ***********/
struct node{
int t,next;
ll w;
int id;
}edge[MAX*2];
int head[MAX],cnt;
void init(int n)
{
cnt=0; memset(head,-1,sizeof(head[0])*(n+1));
}
void addedge(int u,int v,int w,int id)
{
edge[cnt]=node{v,head[u],w,id};
head[u]=cnt++;
}
/************ 树链剖分 **************/
int dep[MAX],dad[MAX],siz[MAX],son[MAX],bev[MAX]; //bev[i]表示第i边对应的点
int top[MAX],rk[MAX],id[MAX];
void dfs1(int u,int pre)
{
siz[u]=1; //u本身
son[u]=-1;
int maxsiz=0; //最大的儿子子树大小
for(int i=head[u];i!=-1;i=edge[i].next)
{
int v=edge[i].t;
if(v==pre)continue;
dep[v]=dep[u]+1;
dad[v]=u;
bev[edge[i].id]=v; //边对应点
dfs1(v,u); //注意dfs的位置
siz[u]+=siz[v];
if(maxsiz<siz[v])
{
maxsiz=siz[v];
son[u]=v;
}
}
}
void dfs2(int u,int fat,int &tag) //结点u,重链顶端
{
top[u]=fat;
rk[u]=++tag; //时间戳
id[tag]=u; //tag对应在树上的结点号
if(son[u]==-1)return; //没有孩子
dfs2(son[u],fat,tag); //优先重链dfs
for(int i=head[u];i!=-1;i=edge[i].next)
{
int v=edge[i].t;
if(v==dad[u]||v==son[u])continue;
dfs2(v,v,tag);
}
}
void cuttree(int root) //将root为根的树链剖分
{
int tag=0;
dep[root]=0; //根的深度和父结点做一个初始化
dad[root]=root; //这是一个假设,根的父结点还是根
dfs1(root,root);
dfs2(root,root,tag);
}
/************* 线段树 ****************/
ll Max[MAX<<2],Min[MAX<<2],mark[MAX<<2]; //mark标记 区间是否取反
void build(int root,int l,int r) //初始线段树
{
mark[root]=0;
Max[root]=-INF;
Min[root]=INF;
if(l==r)return;//叶子节点
int mid=(l+r)/2;
build(root*2,l,mid); //左子树的创建
build(root*2+1,mid+1,r); //右子树
}
void nodeupdate(int root,int l,int r,ll num) //因题而异
{
mark[root]^=1;
swap(Max[root],Min[root]);
Max[root]*=-1;
Min[root]*=-1;
}
void pushdown(int root,int l,int r)//传递给两孩子
{
if(mark[root]==0)return;
int mid=(l+r)/2;
nodeupdate(root*2,l,mid,mark[root]);
nodeupdate(root*2+1,mid+1,r,mark[root]);
mark[root]=0;
}
void pushup(int root,int l,int r)
{
Max[root]=max(Max[root<<1],Max[root<<1|1]);
Min[root]=min(Min[root<<1],Min[root<<1|1]);
}
void update(int left,int right,ll num, int root,int l,int r)//修改
{
if(left<=l&&r<=right){
Max[root]=Min[root]=num;
return;
}
pushdown(root,l,r);
int mid=(l+r)/2;
if(left<=mid)
update(left,right,num,root*2,l,mid);
if(mid<right)
update(left,right,num,root*2+1,mid+1,r);
pushup(root,l,r);
}
void negative(int left,int right, int root,int l,int r)//区间[kl,kr]取反
{
if(left<=l&&r<=right){
nodeupdate(root,l,r,1);
return;
}
pushdown(root,l,r);
int mid=(l+r)/2;
if(left<=mid)
negative(left,right,root*2,l,mid);
if(mid<right)
negative(left,right,root*2+1,mid+1,r);
pushup(root,l,r);
}
ll Qmax(int left,int right, int root,int l,int r)//区间最大值
{
if(left<=l&&r<=right)
return Max[root];
pushdown(root,l,r);
int mid=(l+r)/2;
ll t1=-INF,t2=-INF; ///赋初值!!!
if(left<=mid)
t1=Qmax(left,right,root*2,l,mid);//区间在左子树
if(right>mid)
t2=Qmax(left,right,root*2+1,mid+1,r);//在右子树
return max(t1,t2);
}
/************** 树链->线段树 *********/
void modify(int u,int v,int limit) //更新,negative
{
int fu=top[u],fv=top[v];
while(fu!=fv) //uv不在同一条链上
{
if(dep[fu]>=dep[fv]) //u的深度大
{
negative(rk[fu],rk[u],1,1,limit);
u=dad[fu]; fu=top[u];
}
else
{
negative(rk[fv],rk[v],1,1,limit);
v=dad[fv]; fv=top[v];
}
} //循环结束时,fu-fv在同一链
if(dep[u]>dep[v])swap(u,v);
if(u!=v)negative(rk[u]+1,rk[v],1,1,limit); //注意!边权题,lca不取
}
ll query(int u,int v, int limit) //查询u-v路径
{
ll res=-INF;
int fu=top[u],fv=top[v];
while(fu!=fv) //uv不在同一条链上
{
if(dep[fu]>=dep[fv]) //u的深度大
{
res=max(res,Qmax(rk[fu],rk[u],1,1,limit));
u=dad[fu]; fu=top[u];
}
else
{
res=max(res,Qmax(rk[fv],rk[v],1,1,limit));
v=dad[fv]; fv=top[v];
}
} //循环结束时,fu-fv在同一链
if(dep[u]>dep[v])swap(u,v);
if(u!=v)
res=max(res,Qmax(rk[u]+1,rk[v],1,1,limit)); ///注意+1,lca不取
return res;
}
/************** solve **************/
int a[MAX];
bool solve()
{
int n,u,v,w,k;
scanf("%d",&n);
init(n); //初始图
for(int i=1;i<n;i++) //边权
{
scanf("%d%d%d",&u,&v,&w);
addedge(u,v,w,i);
addedge(v,u,w,i);
}
cuttree(1); //树链剖分
build(1,1,n); //建立线段树
for(int i=1;i<n;i++)a[bev[i]]=edge[(i-1)*2].w; //边权转化为点权
//以下变为点权做法,但是lca结点不可取!!!这是与纯点权图的区别
for(int i=2;i<=n;i++)update(rk[i],rk[i],a[i],1,1,n);//初态
char op[12];
while(scanf("%s",op),op[0]!='D')
{
if(op[0]=='C') //边u权值改为v
{
scanf("%d%d",&u,&v);
update(rk[bev[u]],rk[bev[u]],v,1,1,n);//边对应的点要修改
}
else if(op[0]=='N') //u-v路径权值取相反数
{
scanf("%d%d",&u,&v);
modify(u,v,n); //这里是点之间
}
else //查询区间最大值
{
scanf("%d%d",&u,&v);
int ans=query(u,v,n); //注意这里是查询点之间
printf("%d\n",ans);
}
}
}
int main()
{
int T;
for(scanf("%d",&T);T--;)solve();
}