1. 程式人生 > >JZOJ 5908【NOIP2018模擬10.16】開荒(kaihuang)

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;
}