【bzoj 3531】 [Sdoi2014]旅行
2016-05-18 18:58
501 查看
3531: [Sdoi2014]旅行
Time Limit: 20 Sec Memory Limit: 512 MBSubmit: 1197 Solved: 576
[Submit][Status][Discuss]
Description
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,并记下了途中留宿过
的城市的评级最大值。
由于年代久远,旅行者记下的数字已经遗失了,但记录开始之前每座城市的信仰与评级,还有事件记录本身是完好的。请根据这些信息,还原旅行者记下的数字。 为了方便,我们认为事件之间的间隔足够长,以致在任意一次旅行中,所有城市的评级和信仰保持不变。
Input
输入的第一行包含整数N,Q依次表示城市数和事件数。接下来N行,第i+l行两个整数Wi,Ci依次表示记录开始之前,城市i的
评级和信仰。
接下来N-1行每行两个整数x,y表示一条双向道路。
接下来Q行,每行一个操作,格式如上所述。
Output
对每个QS和QM事件,输出一行,表示旅行者记下的数字。Sample Input
5 63 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
Sample Output
89
11
3
HINT
N,Q < =10^5 , C < =10^5数据保证对所有QS和QM事件,起点和终点城市的信仰相同;在任意时
刻,城市的评级总是不大于10^4的正整数,且宗教值不大于C。
Source
Round 1 Day 1[Submit][Status][Discuss]
【因为每个城市的信仰不同,而旅人只能在与其起点信仰相同的城市留宿,即在查询时,只能将与起点信仰相同的城市的评级计算进和或最大值,所以显然,建一棵线段树不能满足要求,所以要按不同信仰,每种信仰建一棵线段树(参考主席树的方式),每棵线段树维护两个值:和与最大值。查询时直接在相同信仰的线段树中查询即可】
#pragma comment(linker, "/STACK:1024000000,1024000000") #include<cstdio> #include<cstring> #include<iostream> #include<algorithm> using namespace std; int a[200010],next[200010],p[100010],tot;//next数组 int fa[100010],son[100010],size[100010],dep[100010];//fa是存每个节点的父节点,son存每个节点的重儿子,size存每个节点所控制的节点数(包含它自己),dep表示每个节点的深度 int trn1[100010],top[100010],tip;//trn1表示每个节点在线段树里的编号,top表示每个节点所在链的头结点 int tree1[40000010],tree2[40000010],L[4000010],R[4000010],root[100010],g;//tree1是维护和的线段树,tree2是维护最大值的线段树,L是存每棵线段树的左子树的根的标号,R是存每棵线段树的右子树的根的编号,root存每棵线段树的根的编号(即当前线段树是信仰编号为几的线段树) int n,m,w[100010],v[100010];//w是每个城市的评级,v是每个城市的信仰 void add(int x,int y) { tot++; a[tot]=y; next[tot]=p[x]; p[x]=tot; tot++; a[tot]=x; next[tot]=p[y]; p[y]=tot; } void dfs1(int now,int father,int h) { fa[now]=father; dep[now]=h; size[now]=1; int u=p[now]; while (u!=0) { int y=a[u]; if (y!=father) { dfs1(y,now,h+1); size[now]+=size[y]; if (son[now]==-1||size[y]>size[son[now]]) son[now]=y; } u=next[u]; } } void dfs2(int now,int tp) { top[now]=tp; trn1[now]=++tip; if (son[now]==-1) return; dfs2(son[now],tp); int u=p[now]; while (u!=0) { int y=a[u]; if (y!=fa[now]&&y!=son[now]) dfs2(y,y); u=next[u]; } }//树链剖分部分 void updata(int rt) { tree1[rt]=tree1[L[rt]]+tree1[R[rt]]; tree2[rt]=max(tree2[L[rt]],tree2[R[rt]]); } void build(int &rt,int l,int r,int now,int val) { if (!rt) rt=++g;//如果这是一种新的信仰,则新开一棵线段树 if (l==r) { tree1[rt]=val; tree2[rt]=val; return;} int mid=(l+r)>>1; if (now<=mid) build(L[rt],l,mid,now,val); else build(R[rt],mid+1,r,now,val); updata(rt); }//建树和单点修改 int ask1(int rt,int al,int ar,int l,int r) { if (!rt) return 0; if (al<=l&&ar>=r) return tree1[rt]; int mid=(l+r)>>1,ans=0; if (al<=mid) ans+=ask1(L[rt],al,ar,l,mid); if (ar>mid) ans+=ask1(R[rt],al,ar,mid+1,r); return ans; }//查询区间和 int ask2(int rt,int al,int ar,int l,int r) { if (!rt) return 0; if (al<=l&&ar>=r) return tree2[rt]; int mid=(l+r)>>1,ans=-0x7fffffff; if (al<=mid) ans=max(ans,ask2(L[rt],al,ar,l,mid)); if (ar>mid) ans=max(ans,ask2(R[rt],al,ar,mid+1,r)); return ans; }//查询区间最大值 int a1(int x,int y,int rt) { int ans=0; while (top[x]!=top[y]) { if (dep[top[x]]<dep[top[y]]) swap(x,y); ans+=ask1(rt,trn1[top[x]],trn1[x],1,n); x=fa[top[x]]; } if (dep[x]>dep[y]) swap(x,y); ans+=ask1(rt,trn1[x],trn1[y],1,n); return ans; }//查询区间和的预处理(因为x,y可能不在一条链里,序号也不一定谁大谁小,所以要跑一个类似一个lca的东西,当他们不在一条链上时,先将较深的那段跑完,再跑在同一条链上的。 和原来区间修改时相似) int a2(int x,int y,int rt) { int ans=-0x7ffffffff; while (top[x]!=top[y]) { if (dep[top[x]]<dep[top[y]]) swap(x,y); ans=max(ans,ask2(rt,trn1[top[x]],trn1[x],1,n)); x=fa[top[x]]; } if (dep[x]>dep[y]) swap(x,y); ans=max(ans,ask2(rt,trn1[x],trn1[y],1,n)); return ans; }//查询区间最大值的预处理 int main() { int i; memset(son,-1,sizeof(son)); scanf("%d%d",&n,&m); for (i=1;i<=n;++i) scanf("%d%d",&w[i],&v[i]); for (i=1;i<n;++i) {int x,y; scanf("%d%d",&x,&y); add(x,y);} dfs1(1,0,1); dfs2(1,1); for (i=1;i<=n;++i) build(root[v[i]],1,n,trn1[i],w[i]);//按不同信仰建多棵线段树,同一信仰的点放在同一棵线段树里 for (i=1;i<=m;++i) { char c[10]; int x,y; scanf("%s%d%d",c,&x,&y); if (c[0]=='C') if (c[1]=='C') {build(root[v[x]],1,n,trn1[x],0); build(root[y],1,n,trn1[x],w[x]); v[x]=y; continue;}//将编号为x的城市的信仰改为y,即将原来线段树中城市x的评级改为0,再在信仰为y的线段树里加上城市x的评级,最后别忘将存x的信仰的v改为y,不然查询时会出错 else {build(root[v[x]],1,n,trn1[x],y); w[x]=y; continue;}//将编号为x的城市的评级改为y,直接更改即可,不过改完后,也要将w[x]更改成当前值 else if (c[1]=='S') printf("%d\n",a1(x,y,root[v[x]]));//查询旅人从城市x到城市y记录的城市评级的和(只能记录与城市x信仰相同的),直接在以信仰v[x]为根的线段树里查询tree1即可 else printf("%d\n",a2(x,y,root[v[x]]));//查询旅人从城市x到城市y记录的城市评级最大值(只能记录与城市x信仰相同的),直接在以信仰v[x]为根的线段树里查询tree2即可 } return 0; }
相关文章推荐
- Ubuntu下mysql启用远程访问
- Android 通知栏Notification的整合 全面学习
- 人工智能:一种现代方法 第四版 翻译序言
- 有关unity3d与eclipse调试生成debug日志的问题
- 指针,数组与链表
- eclipse下运行自定义maven命令
- 数位dp入门
- SpringMVC原理
- iOS 抽屉效果实现 注意点 及 iOS 控制器间的父子关系 详解
- GCD
- WEB打印
- 绝对定位和相对定位
- 程序员的批评与自我批评(转)
- oracle sql学习一
- bzoj 1003: [ZJOI2006]物流运输(最短路+DP)
- 【NOIP 2015 & SDOI 2016 Round1 & CTSC 2016 & SDOI2016 Round2】游记
- Linear Rendering
- 基于邻域的算法
- CMAKE 学习笔记 (一)
- maven解决jar包版本冲突