[BZOJ]3924 [ZJOI2015] 幻想鄉戰略遊戲 樹鏈剖分

3924: [Zjoi2015]幻想鄉戰略遊戲

Time Limit: 100 Sec  Memory Limit: 256 MB
Submit: 1034  Solved: 478
[Submit][Status][Discuss]

Description

 傲嬌少女幽香正在玩一個非常有趣的戰略類遊戲,本來這個遊戲的地圖其實還不算太大,幽香還能管得過來,但是不知道爲什麼現在的網遊廠商把遊戲的地圖越做越大,以至於幽香一眼根本看不過來,更別說和別人打仗了。 在打仗之前,幽香現在面臨一個非常基本的管理問題需要解決。 整個地圖是一個樹結構,一共有n塊空地,這些空地被n-1條帶權邊連接起來,使得每兩個點之間有一條唯一的路徑將它們連接起來。在遊戲中,幽香可能在空地上增加或者減少一些軍隊。同時,幽香可以在一個空地上放置一個補給站。 如果補給站在點u上,並且空地v上有dv個單位的軍隊,那麼幽香每天就要花費dv×dist(u,v)的金錢來補給這些軍隊。由於幽香需要補給所有的軍隊,因此幽香總共就要花費爲Sigma(Dv*dist(u,v),其中1<=V<=N)的代價。其中dist(u,v)表示u個v在樹上的距離(唯一路徑的權和)。 因爲遊戲的規定,幽香只能選擇一個空地作爲補給站。在遊戲的過程中,幽香可能會在某些空地上製造一些軍隊,也可能會減少某些空地上的軍隊,進行了這樣的操作以後,出於經濟上的考慮,幽香往往可以移動他的補給站從而省一些錢。但是由於這個遊戲的地圖是在太大了,幽香無法輕易的進行最優的安排,你能幫幫她嗎? 你可以假定一開始所有空地上都沒有軍隊。

PDF版試題:JudgeOnline/upload/201708/zjoi2015d1.pdf

Input

第一行兩個數n和Q分別表示樹的點數和幽香操作的個數,其中點從1到n標號。 
接下來n-1行,每行三個正整數a,b,c,表示a和b之間有一條邊權爲c的邊。 
接下來Q行,每行兩個數u,e,表示幽香在點u上放了e單位個軍隊
(如果e<0,就相當於是幽香在u上減少了|e|單位個軍隊,說白了就是du←du+e)。
數據保證任何時刻每個點上的軍隊數量都是非負的。 
1<=c<=1000, 0<=|e|<=1000, n<=10^5, Q<=10^5
對於所有數據,這個樹上所有點的度數都不超過20
N,Q>=1

Output

 對於幽香的每個操作,輸出操作完成以後,每天的最小花費,也即如果幽香選擇最優的補給點進行補給時的花費。 

Sample Input

10 5
1 2 1
2 3 1
2 4 1
1 5 1
2 6 1
2 7 1
5 8 1
7 9 1
1 10 1
3 1
2 1
8 1
3 1
4 1

Sample Output

0
1
4
5
6

HINT



Source

[Submit][Status][Discuss]


HOME Back

  這道題我就呵呵了, 坑我一晚上 + 一下午. 昨天晚上狀態極不好邊想這道題邊去頹課件去了, 導致效率極低. 最後集中想了20min沒想出來選擇看題解, 發現題解清一色的拋結論... 於是自己證了一下, 由於有個sigma寫錯了導致我推了1年... 後面才發現是傻逼結論. 然而一個晚上就這麼在頹廢中過去了QAQ... 不過網上都用的點分治|樹鏈剖分 + 點分治. 昨晚回寢室睡覺時想到了一個純樹鏈剖分的做法. 於是今天下午來實現... 40min寫完沒想到被坑了一下午.

  然而這道題是ZJOI2015 Day1 T1. 我選擇go die. 講真t3比t1簡單啊(可能是因爲我沒往性質結論上想...

  這道題要求的東西實際上就是帶權重心. 爲什麼呢? 我們設sum[u]爲u子樹的d之和(d詳情請看題目定義). 那麼如果u的兒子v, sum[v] * 2 >= sum[root]的話那麼最優點一定在v的子樹裏. 證明: 我們考慮以u作爲補給點的花費設爲p, 那麼我們會發現若以v作爲補給點的話花費是 p + (sum[root] - sum[v] * 2) * w[u, v]. 因爲走過(u, v)這條邊會使除了v子樹的其他點i的所有花費+d[i] * w(u, v), 那麼就是(sum[root] - sum[v]) * w(u, v). 然後v子樹裏的所有點的花費會 - sum[v] * w(u, v). 加起來就是上式辣. 那麼如果sum[v] * 2 >= sum[root]的話花費就會減少. 而且這樣的v由於sum[v]>=sum[u]/2所以只會最多有一個. 爲什麼不在其他子樹裏呢? 因爲其他子樹譬如v1滿足sum[v1] < sum[root]/2那麼v1子樹裏的點sum更小顯然都滿足, 那麼花費只會增加不會減少. 所以我們只需要順着根往下判斷來找這個最優點就行了(直到兒子都比自己劣那此時就是最優點).

  然後我們會發現會tle. 考慮怎麼優化. 首先從根到最優點的路徑是唯一確定的, 並且顯然這條路徑是垂直的鏈, 也就是說深度單增且dfs序單增. 並且從u要走到v的話肯定滿足v是u中sum最大的. 那麼我們用線段樹維護dfs序區間max就可以啦. 每次判斷即可(這個很好理解可以結合代碼seg_query函數). 對於修改直接修改一條鏈的sum即可. 顯然這可以用樹鏈剖分來維護. 那怎麼算答案呢?  我們知道每次移動從u到v花費差距是 (sum[root] - sum[v] * 2) * w[u, v]. 那麼最優點與根的差距就是sum[root] * dis[v](到根距離) - 2 * (sum[v] * w(fa[v], v). 根的答案很好維護, 式子前面一項也很好維護. 第二項發現只跟v有關, 且只有sum一個在變. 那麼樹剖的時候順帶維護一下就好了. 因爲每次修改鏈上都是區間加減, 對於一段dfs序區間的sum[v] * w(fa[v], v)之和(v屬於這段區間)也就是加上了這段區間每個v到各自父親的距離 * 當前改動的值. 詳見代碼(非常easy辣).

  然而我老是wa的原因就是之前推導的時候沒考慮邊權, 直接用的dep... 然而樣例恰好邊權都是1, 我...

  本來以爲A了可以艹榜的, 發現只能在第二頁(我的ID是lkq的, 我沒有權限號...)... 我還是太naive了. 改天會了zkw線段樹再回來艹一發.

  話說爲什麼加了const &和fread讀優還沒有沒加快啊. 而且Claris神犇什麼鬼畜壓行怎麼又寫了樹剖+線段樹還寫了點分治都只有80多行啊

#include<bits/stdc++.h>
using namespace std;
typedef long long lnt;
const int maxn = 2e5 + 5;
lnt ans, all;
int n, Q, num, idx;
int siz[maxn], fa[maxn], son[maxn], seq[maxn], in[maxn], h[maxn], top[maxn], dis[maxn], dep[maxn], par[maxn];
inline const int read() {
	register int x = 0, f = 1;
	register char ch = getchar();
	while (ch < '0' || ch > '9') {if (ch == '-') f = -1; ch = getchar();}
	while (ch >= '0' && ch <= '9') x = (x << 3) + (x << 1) + ch - '0', ch = getchar();
	return f * x;
}
struct edge {
	int nxt, v, w;
}e[maxn << 1];
inline void add(int u, int v, int w) {
	e[++ num].v = v, e[num].nxt = h[u], e[num].w = w, h[u] = num;
	e[++ num].v = u, e[num].nxt = h[v], e[num].w = w, h[v] = num;
}
void dfs1(int u, int f) {
	siz[u] = 1, fa[u] = f;
	for (int i = h[u]; i; i = e[i].nxt) {
		int v = e[i].v;
		if (v == f) continue;
		par[v] = e[i].w;
		dep[v] = dep[u] + 1;
		dis[v] = dis[u] + e[i].w;
		dfs1(v, u);
		siz[u] += siz[v];
		if (siz[son[u]] < siz[v]) son[u] = v;
	}
}
void dfs2(int u, int tp) {
	top[u] = tp;
	in[u] = ++ idx, seq[idx] = u;
	if (son[u]) dfs2(son[u], tp);
	for (int i = h[u]; i; i = e[i].nxt) {
		int v = e[i].v;
		if (v == fa[u] || v == son[u]) continue;
		dfs2(v, v);
	}
}
struct node {
	node *ls, *rs;
	int mid; lnt sum, flag, cmax, dsm;
	inline void pushdown(int lf, int rg) {
		if (flag) {
			mid = (lf + rg) >> 1;
			ls -> sum += flag * ls -> dsm;
			rs -> sum += flag * rs -> dsm;
			ls -> cmax += flag, rs -> cmax += flag;
			ls -> flag += flag, rs -> flag += flag;
			flag = 0;
		}
	}
	inline void update() {
		sum = ls -> sum + rs -> sum;
		cmax = (ls -> cmax > rs -> cmax) ? ls -> cmax : rs -> cmax;
	}
}pool[maxn << 1], *root, *tail = pool;
node* build(int lf, int rg) {
	node* bt = ++ tail;
	if (lf == rg) {
		bt -> dsm = par[seq[lf]];
		bt -> cmax = bt -> sum = 0;
		return bt;
	}
	int mid = (lf + rg) >> 1;
	bt -> ls = build(lf , mid), bt -> rs = build(mid + 1, rg);
	bt -> update();
	bt -> dsm = bt -> ls -> dsm + bt -> rs -> dsm;
	return bt;
}
void modify(node* bt, int lf, int rg,  int L, int R, int val) {
	if (L <= lf && rg <= R) {
		bt -> cmax += val;
		bt -> flag += val;
		bt -> sum += bt -> dsm * val;
		return;
	}
	bt -> pushdown(lf, rg);
	int mid = (lf + rg) >> 1;
	if (L <= mid) modify(bt -> ls, lf, mid, L, R, val);
	if (R > mid) modify(bt -> rs, mid + 1, rg, L, R, val);
	bt -> update(); 
}
lnt query(node* bt, int lf, int rg, int L, int R) {
	if (L <= lf && rg <= R) return bt -> sum;
	bt -> pushdown(lf, rg);
	int mid = (lf + rg) >> 1; lnt rt = 0;
	if (L <= mid) rt += query(bt -> ls, lf, mid, L, R);
	if (R > mid) rt += query(bt -> rs, mid + 1, rg, L, R);
	bt -> update();
	return rt;
}
int queryheart(node* bt, int lf, int rg) {
	if (lf == rg) return seq[lf];
	bt -> pushdown(lf, rg);
	int mid = (lf + rg) >> 1, rt = 0;
	if (all <= 2 * bt -> rs -> cmax) rt = queryheart(bt -> rs, mid + 1, rg);
	else rt = queryheart(bt -> ls, lf, mid);
	bt -> update();
	return rt;
}
inline void seg_modify(int u, int val) {
	while (u)
		modify(root, 1, n, in[top[u]], in[u], val), u = fa[top[u]];
}
inline lnt seg_query() {
	lnt rt = 0;
	int u = queryheart(root, 1, n), ori = u;
	while (u) 
		rt += query(root, 1, n, in[top[u]], in[u]), u = fa[top[u]];
	return all * dis[ori] - 2 * rt;
}
int main() {
	n = read(), Q = read();
	register int i, u, v, w;
	for (i = 1; i < n; ++ i)
		u = read(), v = read(), w = read(), add(u, v, w);
	dfs1(1, 0), dfs2(1, 1);
	root = build(1, n);
	for (i = 1; i <= Q; ++ i) {
		u = read(), w = read();
		seg_modify(u, w);
		all += w, ans += 1ll * dis[u] * w;
		lnt hh = (ans + seg_query());
		printf("%lld\n", hh);
	}
	return 0;
}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章