【NOIP2018模擬賽2018.10.18】開荒
題目
Description
題目背景: 尊者神高達作為一個萌新,在升級路上死亡無數次後被一隻大黃嘰帶回了師門。他加入師門後發現有無窮無盡的師兄弟姐妹,這幾天新副本開了,尊者神高達的師門作為一個 pve師門,於是他們決定組織一起去開荒。
題目描述: 師門可以看做以 1 為根的一棵樹,師門中的每一個人都有一定的裝備分數。一共會有 q 個事件。每個事件可能是一次開荒,也可能是因為開荒出了好裝備而導致一個人的裝分出現了變化。對於一次開荒,會有 k 個人組織,由於師門的號召力很強,所以所有在組織者中任意兩個人簡單路徑上的人都會參加。
Input
第一行 n ,q; 接下來 1 行 n 個數,代表每個人的分值; 接下來 n-1 行 u,v 代表一條邊 接下來 q 行 Q 代表詢問,接下來 k 個數代表組織的人數,讀入為 0時停止讀入。 C 代表修改,輸入 x,w 代表將 x 的分值變為 w
Output
共 Q 的數量行,為開荒的人的總分值
Sample Input
4 4
10 5 2 2
1 2
2 3
2 4
Q 3 4 0
C 3 200
Q 3 4 0
Q 1 4 0
Sample Output
9
207
17
樣例解釋:
第一次詢問,參加的人有 2,3,4 5+2+2=9
第一次修改,權值為 10 5 200 2
第二次詢問,參加的人有 2,3,4 5+200+2=207
第三次詢問,參加的人有 1,2,4 10+5+2=17
Data Constraint
資料範圍:
20%的資料 n<=10000,q<=500;
另外 20%的資料 k=2
另外 20%的資料 沒有修改操作
所有資料 n,q<=100000,所有詢問 k 的和<=1000000
保證資料合法
題解
–長知識了
首先是虛樹:只把lca中要用到的有用點拿出來,重新建的一棵樹
具體實現主要是按dfs序排序的棧的維護
發現在統計答案的時候就是dfs序相鄰的兩個點之間的路徑上的和
又可以用樹狀陣列維護(差分的思想)
修改也可以O(1)在樹狀陣列上處理了
程式碼
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include <cmath>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN=1e5+5;
int n,q;
int head[MAXN],next[MAXN*2],to[MAXN*2],cnt;
long long w[MAXN],s[MAXN];
int fa[MAXN][20],d[MAXN],dfn[MAXN],last[MAXN],sum;
char opt;
int a[MAXN*2],k,tot;
int stack[MAXN],top;
long long ans;
void add(int u,int v){
cnt++;
next[cnt]=head[u];
to[cnt]=v;
head[u]=cnt;
}
void build(int x,int F){
dfn[x]=++sum;
for(int i=1;i<=18;i++)
fa[x][i]=fa[fa[x][i-1]][i-1];
for(int i=head[x];i;i=next[i]){
int y=to[i];
if(y==F)
continue;
d[y]=d[x]+1;
fa[y][0]=x;
build(y,x);
}
last[x]=sum;
}
void putin(int x,long long y){
for(x;x<=n;x+=x&-x)
s[x]+=y;
}
bool comp(const int &a,const int &b){
return dfn[a]<dfn[b];
}
int lca(int x,int y){
if(d[x]<d[y])
swap(x,y);
for(int i=18;i>=0;i--)
if(d[fa[x][i]]>=d[y])
x=fa[x][i];
if(x==y)
return x;
for(int i=18;i>=0;i--)
if(fa[x][i]!=fa[y][i]){
x=fa[x][i];
y=fa[y][i];
}
return fa[x][0];
}
long long ask(int x){
long long ans=0;
for(x;x;x-=x&-x)
ans+=s[x];
return ans;
}
int main(){
// freopen("kaihuang.in","r",stdin);
// freopen("kaihuang.out","w",stdout);
cin>>n>>q;
for(int i=1;i<=n;i++)
scanf("%lld",&w[i]);
for(int i=1;i<n;i++){
int u,v;
scanf("%d%d",&u,&v);
add(u,v);
add(v,u);
}
d[1]=1;
build(1,0);
for(int i=1;i<=n;i++){
putin(dfn[i],w[i]);
putin(last[i]+1,-w[i]);
}
while(q--){
cin>>opt;
if(opt=='Q'){
k=0;
while(1){
scanf("%d",&a[++k]);
if(!a[k]){
k--;
break;
}
}
sort(a+1,a+1+k,comp);
tot=k;
for(int i=1;i<tot;i++)
a[++k]=lca(a[i],a[i+1]);
sort(a+1,a+1+k,comp);
k=unique(a+1,a+1+k)-a-1;
ans=0;
top=0;
for(int i=1;i<=k;i++){
while(top&&last[stack[top]]<dfn[a[i]])
top--;
if(top)
ans+=ask(dfn[a[i]])-ask(dfn[stack[top]]);
else
ans+=w[a[i]];
stack[++top]=a[i];
}
printf("%lld\n",ans);
}
else{
int x;
long long W;
scanf("%d%lld",&x,&W);
swap(W,w[x]);
W=w[x]-W;
putin(dfn[x],W);
putin(last[x]+1,-W);
}
}
return 0;
}