JZOJ 5908【NOIP2018模擬10.16】開荒(kaihuang)
神奇的樹剖 + 利用一次詢問多條路徑 話不多說先放題面
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
保證資料合法
這道題樹剖很顯然嘛 但我們一次要求多條路徑
我的暴力想法—— laz 陣列存多條路徑走過的點 權值改為 1
為了剪枝 我把子節點有經過的點設為了 2 沒有的就是 0 這樣可以大大提高效率
找到 2 繼續往下找 找到 1 直接返回線段樹的值 找到 0 返回 0
於是就有了三十分 (預測沒加優化只有 20 分 第三個點 700ms 卡過)
暴力程式碼
#include <algorithm> #include <cstring> #include <cstdio> #define ll long long using namespace std; const int MAXN = 100010; struct edge{int next,to;}e[MAXN << 1]; ll tr[MAXN << 3]; int siz[MAXN],dep[MAXN],fa[MAXN],son[MAXN],top[MAXN],id[MAXN],oid[MAXN]; int first[MAXN],v[MAXN],laz[MAXN << 3],tot; inline int re() { char q = getchar(); int x = 0; while (q < '0' || q > '9') q = getchar(); while ('0' <= q && q <= '9') x = (x << 3) + (x << 1) + q - (3 << 4),q = getchar(); return x; } inline void add(int x,int y) { e[++tot].next = first[x]; e[tot].to = y; first[x] = tot; } inline void dfs1(int p) { dep[p] = dep[fa[p]] + 1; ++siz[p]; for (int a = first[p],b = e[a].to ; a ; a = e[a].next,b = e[a].to) if (b == fa[p]) continue; else { fa[b] = p; dfs1(b); siz[p] += siz[b]; if (siz[b] > siz[son[p]]) son[p] = b; } } inline void dfs2(int p,int f) { top[p] = f; id[p] = ++tot; oid[tot] = p; if (!son[p]) return; dfs2(son[p],f); for (int a = first[p],b = e[a].to ; a ; a = e[a].next,b = e[a].to) if (b != fa[p] && b != son[p]) dfs2(b,b); } inline void build(int l,int r,int len) { if (l == r) {tr[len] = v[oid[l]]; return;} int mid = (l + r) >> 1; build(l,mid++,len << 1); build(mid,r,len << 1 | 1); tr[len] = tr[len << 1] + tr[len << 1 | 1]; } inline void update(int l,int r,int len,int i,int j) { if (l == r && l == i) {tr[len] = j; return;} int mid = (l + r) >> 1; if (i <= mid) update(l,mid,len << 1,i,j); else update(++mid,r,len << 1 | 1,i,j); tr[len] = tr[len << 1] + tr[len << 1 | 1]; } inline void uplaze(int l,int r,int len,int i,int j) { if (laz[len] & 1) return; if (i <= l && r <= j) {laz[len] = 1; return;} laz[len] = 2; int mid = (l + r) >> 1; if (i <= mid) uplaze(l,mid,len << 1,i,j); if (mid < j) uplaze(++mid,r,len << 1 | 1,i,j); } inline ll rebuild(int l,int r,int len) { if (!laz[len]) return 0; if (laz[len] & 1) return tr[len]; int mid = (l + r) >> 1; return rebuild(l,mid,len << 1) + rebuild(++mid,r,len << 1 | 1); } void lpcate(int x,int y) { while (top[x] != top[y]) { if (dep[top[x]] < dep[top[y]]) swap(x,y); uplaze(1,siz[1],1,id[top[x]],id[x]); x = fa[top[x]]; } if (dep[x] > dep[y]) swap(x,y); uplaze(1,siz[1],1,id[x],id[y]); } ll uplone(int l,int r,int len,int i) { if (l == r && l == i) return tr[len]; int mid = (l + r) >> 1; if (i <= mid) return uplone(l,mid,len << 1,i); return uplone(++mid,r,len << 1 | 1,i); } int main() { freopen("kaihuang.in","r",stdin); freopen("kaihuang.out","w",stdout); int n = re(),q = re(),x,y; for (int a = 1 ; a <= n ; ++ a) v[a] = re(); for (int a = 1 ; a < n ; ++ a) x = re(),y = re(), add(x,y),add(y,x); tot = 0; dfs1(1),dfs2(1,1); build(1,n,1); while (q--) { char w = getchar(); while (w != 'Q' && w != 'C') w = getchar(); if (w == 'Q') { memset(laz,0,sizeof(laz)); for (n = 1,v[n] = re() ; v[n] ; ++ n,v[n] = re()); for (int a = 2 ; a < n ; ++ a) for (int b = 1 ; b < a ; ++ b) lpcate(v[b],v[a]); if (n > 2) printf("%lld\n",rebuild(1,siz[1],1)); else printf("%lld\n",uplone(1,siz[1],1,id[v[1]])); continue; } x = re(),y = re(),update(1,siz[1],1,id[x],y); } fclose(stdin); fclose(stdout); return 0; }
好了好了做題怎麼能不發正解呢
正解就是多條路徑按 dfs 序 排然後求 lca 可使每個點經過兩遍 兩點 lca (可能除最頂上的) 經過三遍 這個手推可以發現 證明的話.....進點一次 跳到第二個點就兩次 第 lca>該兩點的點再跳就三次 每次減去一個 每個點就變兩次了 最上面的次數就是 lca 為該點的節點對數 每次減去一個就為 0 了 所以要加上兩個該點權
根據這個 我們可以直接求路徑距離 然後減去相鄰兩點 lca 的權值 最後加上兩個所有點最頂上的 lca (也是所有點的 lca) 的權值
其他部分不變 不過單點路徑不用特判了(本來就不是那樣做的).....話說我的暴力打的也是累
下放正解
#include <algorithm>
#include <cstring>
#include <cstdio>
#define ll long long
using namespace std;
const int MAXN = 100010;
struct edge{int next,to;}e[MAXN << 1];
ll tr[MAXN << 3],mx;
int siz[MAXN],dep[MAXN],fa[MAXN],son[MAXN],top[MAXN],id[MAXN],oid[MAXN];
int first[MAXN],v[MAXN],bot[MAXN],tot;
inline short cmp(int x,int y) {return id[x] < id[y];}
inline int r() {
char q = getchar(); int x = 0;
while (q < '0' || q > '9') q = getchar();
while ('0' <= q && q <= '9') x = (x << 3) + (x << 1) + q - (3 << 4),q = getchar();
return x;
}
inline void add(int x,int y) {
e[++tot].next = first[x];
e[tot].to = y;
first[x] = tot;
}
inline void dfs1(int p) {
dep[p] = dep[fa[p]] + 1;
++siz[p];
for (int a = first[p],b = e[a].to ; a ; a = e[a].next,b = e[a].to)
if (b == fa[p]) continue; else {
fa[b] = p;
dfs1(b);
siz[p] += siz[b];
if (siz[b] > siz[son[p]]) son[p] = b;
}
}
inline void dfs2(int p,int f) {
top[p] = f;
id[p] = ++tot;
oid[tot] = p;
if (!son[p]) return;
dfs2(son[p],f);
for (int a = first[p],b = e[a].to ; a ; a = e[a].next,b = e[a].to)
if (b != fa[p] && b != son[p]) dfs2(b,b);
}
inline void build(int l,int r,int len) {
if (l == r) {tr[len] = v[oid[l]]; return;}
int mid = (l + r) >> 1;
build(l,mid,len << 1);
build(++mid,r,len << 1 | 1);
tr[len] = tr[len << 1] + tr[len << 1 | 1];
}
inline void update(int l,int r,int len,int i,int j) {
if (l == r && l == i) {tr[len] = j; return;}
int mid = (l + r) >> 1;
if (i <= mid) update(l,mid,len << 1,i,j);
else update(++mid,r,len << 1 | 1,i,j);
tr[len] = tr[len << 1] + tr[len << 1 | 1];
}
inline ll get(int l,int r,int len,int i,int j) {
if (i <= l && r <= j) return tr[len];
int mid = (l + r) >> 1; ll ans = 0;
if (i <= mid) ans += get(l,mid,len << 1,i,j);
if (mid < j) ans += get(++mid,r,len << 1 | 1,i,j);
return ans;
}
inline ll lca(int x,int y) {
ll ans = 0;
while (top[x] != top[y])
{
if (dep[top[x]] < dep[top[y]]) swap(x,y);
ans += get(1,siz[1],1,id[top[x]],id[x]);
x = fa[top[x]];
}
if (dep[x] > dep[y]) swap(x,y);
return ans - v[x] + get(1,siz[1],1,id[x],id[y]);
}
inline int mxlca(int x,int y){
while (top[x] != top[y])
{
if (dep[top[x]] < dep[top[y]]) swap(x,y);
x = fa[top[x]];
}
return dep[x] < dep[y] ? x : y;
}
int main()
{
freopen("kaihuang.in","r",stdin);
freopen("kaihuang.out","w",stdout);
int n = r(),q = r(),x,y;
for (int a = 1 ; a <= n ; ++ a) v[a] = r();
for (int a = 1 ; a < n ; ++ a) x = r(),y = r(),
add(x,y),add(y,x); tot = 0;
dfs1(1),dfs2(1,1);
build(1,n,1);
while (q--)
{
char w = getchar();
while (w != 'Q' && w != 'C') w = getchar();
if (w == 'Q')
{
for (n = 1,bot[n] = r() ; bot[n] ; ++ n,bot[n] = r());
sort(bot + 1,bot + n,cmp);
ll ans = bot[1];
for (int a = 2 ; a < n ; ++ a) ans = mxlca(ans,bot[a]);
ans = v[ans] << 1;
for (int a = 2 ; a < n ; ++ a) ans += lca(bot[a - 1],bot[a]);
ans += lca(bot[1],bot[--n]);
printf("%lld\n",ans >> 1);
continue;
}
x = r(),y = r(),v[x] = y,update(1,siz[1],1,id[x],y);
}
fclose(stdin);
fclose(stdout);
return 0;
}