1. 程式人生 > >1059E】Split the Tree

1059E】Split the Tree

@題目描述 - [email protected]

time limit per test:2 seconds
memory limit per test:256 megabytes

You are given a rooted tree on n vertices, its root is the vertex number 1. The i-th vertex contains a number wi. Split it into the minimum possible number of vertical paths in such a way that each path contains no more than L vertices and the sum of integers wi on each path does not exceed S. Each vertex should belong to exactly one path.
A vertical path is a sequence of vertices v1,v2,…,vk where vi (i≥2) is the parent of vi−1.

Input
The first line contains three integers n, L, S (1≤n≤105,1≤L≤105, 1≤S≤1018) — the number of vertices, the maximum number of vertices in one path and the maximum sum in one path.
The second line contains n integers w1,w2,…,wn (1≤wi≤109) — the numbers in the vertices of the tree.
The third line contains n−1 integers p2,…,pn (1≤pi<i), where pi is the parent of the i-th vertex in the tree.

Output
Output one number — the minimum number of vertical paths. If it is impossible to split the tree, output −1.

Examples
input
3 1 3
1 2 3
1 1
output
3

input
3 3 6
1 2 3
1 1
output
2

input
1 1 10000
10001
output
-1

Note
In the first sample the tree is split into {1}, {2}, {3}.
In the second sample the tree is split into {1, 2}, {3} or {1, 3}, {2}.
In the third sample it is impossible to split the tree.

@題目翻譯@

將一棵n個點的帶權有根樹剖分成儘量少的鏈,使得(1)鏈的兩個端點是祖先關係(2)鏈含有的頂點個數小於等於L(3)鏈上所有點的點權和小於等於S。
求出最少鏈的數量,如果無解輸出-1。N<=105

@分析@

【當時比賽的時候我真的想到了正解來著……】
【只是自己帶了一個“樹上倍增永遠寫不對”的buff……所以沒有A掉QAQ。】
【後來就去調D題。然後D題平方差公式推錯……整場比賽就崩掉了QAQ……】

首先,如果所有的鏈都只包含一個結點,那麼如果存在結點i使得它的點權wi>S則無解,否則就有解。

我們令 a[i] 表示將 i 這顆子樹按題意剖分得到的最小鏈條數,再令 sum[i]=(jjia[j])sum[i] = (\sum_j^{j是i的孩子}a[j])。不難發現 a[i] 有上界: a[i]&lt;=sum[i]+1a[i]&lt;=sum[i]+1。上式表示將 i 單獨剖分成一條鏈的鏈數。再進一步思考,a[i] 也有下界:sum[i]&lt;=a[i]sum[i]&lt;=a[i]。上式表示增加一個結點過後,鏈的數量不會變少。

於是:a[i]=sum[i]sum[i]+1a[i]=sum[i]或sum[i]+1
【為簡化描述,如果a[i]=sum[i]a[i]=sum[i]我們就稱 i 為黑點,否則為灰點。】

如果結點 i 是黑點,則它不能夠單獨成鏈,那麼它肯定與它的某一個孩子共鏈。所以有這樣一個結論:最優策略下,鏈底端的結點 d 一定滿足灰點。

再一步推導,就能得到一個關鍵的結論:在以結點 i 為根的子樹中,如果存在灰點 d ,使得 i~d 是一條合法的鏈,則 i 為黑點。這個結論可以用歸納法證明(當然也可以猜測它是對的,跳過證明)。

然後,根據上面那個結論可知,灰點一定是在鏈的底端。所以有鏈的數量 = 灰點數量。

那麼,現在開始描述演算法:從根節點開始dfs。將所有的兒子都遍歷完後,如果當前點 rt 為灰點,則沿著 rt 往上走,直到走到 p 使得 p ~ rt這條鏈不合法。然後 p ~ rt上的(除p,rt以外)點記為黑點;否則如果 rt 為黑點,不作任何操作。

找結點 p 可以用倍增法。標記 p~rt 上的點可以用樹鏈剖分。

@程式碼@

啊啊啊……總覺得還是很可惜啊QAQ
考場上寫的程式碼略醜,請大家見諒。如果大家還有什麼疑問或者我的程式錯了,可以評論在下面。
我會盡心回覆大家的評論的qwq!

#include<cstdio>
typedef long long ll;
const int MAXN = 100000;
const ll INF = (1LL<<60);
struct edge{
	int to;
	edge *nxt;
}edges[MAXN + 5], *adj[MAXN + 5], *ecnt=&edges[0];
void addedge(int u, int v) {
	edge *p=(++ecnt);
	p->to = v, p->nxt = adj[u], adj[u] = p;
}
int n, L; ll w[MAXN + 5], S;
int fa[MAXN + 5], dep[MAXN + 5], siz[MAXN + 5];
int fir[MAXN + 5], dfn[MAXN + 5], top[MAXN + 5], dcnt = 0;
void dfs1(int rt, int pre) {
	fa[rt] = pre, dep[rt] = dep[pre] + 1, siz[rt] = 1;
	for(edge *p=adj[rt];p!=NULL;p=p->nxt) {
		if( p->to == pre ) continue;
		dfs1(p->to, rt);
		siz[rt] += siz[p->to];
	}
}
void dfs2(int rt, int tp) {
	top[rt] = tp, fir[rt] = (++dcnt), dfn[dcnt] = rt;
	int hvy = 0;
	for(edge *p=adj[rt];p!=NULL;p=p->nxt) {
		if( p->to == fa[rt] ) continue;
		if( hvy == 0 || siz[p->to] > siz[hvy] )
			hvy = p->to;
	}
	if( !hvy ) return ;
	dfs2(hvy, tp);
	for(edge *p=adj[rt];p!=NULL;p=p->nxt)
		if( p->to != fa[rt] && p->to != hvy )
			dfs2(p->to, p->to);
}
struct node{
	int le, ri;
	bool tag;
}tree[4*MAXN + 5];
void Build(int x, int l, int r) {
	tree[x].le = l, tree[x].ri = r;
	if( l == r ) return ;
	int Mid = (l + r) >> 1;
	Build(x<<1, l, Mid);
	Build(x<<1|1, Mid+1, r);
}
void Modify(int x, int l, int r) {
	if( l > tree[x].ri || r < tree[x].le )
		return ;
	if( l <= tree[x].le && tree[x].ri <= r ) {
		tree[x].tag = true;
		return ;
	}
	if( tree[x].tag ) return ;
	Modify(x<<1, l, r);
	Modify(x<<1|1, l, r);
}
bool Query(int x, int pos) {
	if( pos > tree[x].ri || pos < tree[x].le )
		return false;
	if( pos == tree[x].le && pos == tree[x].ri ) return tree[x].tag;
	if( tree[x].tag ) return true;
	else return Query(x<<1, pos) || Query(x<<1|1, pos);
}
void TModify(int u, int v) {
	while( top[u] != top[v] ) {
		if( dep[top[u]] > dep[top[v]] ) {
			Modify(1, fir[top[u]], fir[u]);
			u = fa[top[u]];
		}
		else {
			Modify(1, fir[top[v]], fir[v]);
			v = fa[top[v]];
		}
	}
	if( dep[u] > dep[v] )
		Modify(1, fir[v], fir[u]);
	else Modify(1, fir[u], fir[v]);
}
int fa1[MAXN + 5][25];
ll sum[MAXN + 5][25];
void dfs(int rt) {
	for(int i=1;i<20;i++) {
		fa1[rt][i] = fa1[fa1[rt][i-1]][i-1];
		if( sum[rt][i-1] != INF && sum[fa1[rt][i-1]][i-1] != INF )
			sum[rt][i] = sum[rt][i-1] + sum[fa1[rt][i-1]][i-1];
		else sum[rt][i] = INF;
	}
	for(edge *p=adj[rt];p!=NULL;p=p->nxt) {
		fa1[p->to][0] = rt;
		sum[p->to][0] = w[rt];
		dfs(p->to);
	}
}
int UpTo(int u, ll lim, int dep) {
	dep--; lim -= w[u];
    for(int i=19;i>=0;i--) {
        if( sum[u][i] <= lim && dep >= (1<<i) ) {
            lim -= sum[u][i];
            dep -= (1<<i);
            u = fa1[u][i];
        }
    }
    return u;
}
int ans = 0;
void dfs3(int rt) {
	for(edge *p=adj[rt];p!=NULL;p=p->nxt)
		dfs3(p->to);
	if( !Query(1, fir[rt]) ) {
		TModify(rt, UpTo(rt, S, L));
		ans++;
	}
}
int main() {
	scanf("%d%d%I64d", &n, &L, &S);
	for(int i=1;i<=n;i++)
		scanf("%I64d", &w[i]);
	for(int i=2;i<=n;i++) {
		int p;
		scanf("%d", &p);
		addedge(p, i);
	}
	for(int i=1;i<=n;i++) {
		if( w[i] > S ) {
			printf("%d\n", -1);
			return 0;
		}
	}
	for(int i=0;i<20;i++) {
        sum[0][i] = (1LL<<60);
        fa1[0][i] = 0;
    }
    sum[1][0] = (1LL<<60);
	dfs1(1, 0); dfs2(1, 1); Build(1, 1, n); dfs(1); dfs3(1);
	printf("%d\n", ans);
}

@[email protected]

就是這樣,新的一天裡,也請多多關照哦(ノω<。)ノ))☆.。