1059E Split the Tree(貪心+樹上倍增)
E. Split the Tree
time limit per test
2 seconds
memory limit per test
256 megabytes
input
standard input
output
standard output
You are given a rooted tree on $$$n$$$ vertices, its root is the vertex number $$$1$$$. The $$$i$$$-th vertex contains a number $$$w_i$$$. 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 $$$w_i$$$ on each path does not exceed $$$S$$$. Each vertex should belong to exactly one path.
A vertical path is a sequence of vertices $$$v_1, v_2, \ldots, v_k$$$ where $$$v_i$$$ ($$$i \ge 2$$$) is the parent of $$$v_{i - 1}$$$.
Input
The first line contains three integers $$$n$$$, $$$L$$$, $$$S$$$ ($$$1 \le n \le 10^5$$$, $$$1 \le L \le 10^5$$$, $$$1 \le S \le 10^{18}$$$) — 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 $$$w_1, w_2, \ldots, w_n$$$ ($$$1 \le w_i \le 10^9$$$) — the numbers in the vertices of the tree.
The third line contains $$$n - 1$$$ integers $$$p_2, \ldots, p_n$$$ ($$$1 \le p_i < i$$$), where $$$p_i$$$ 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.
題意:給你一棵樹,問你最多能把這棵樹分成多少條鏈,使得每條鏈的長度不超過L,每條鏈上的點的權值和不超過S。這裡的鏈,是不能跨過子樹的根節點的鏈(樹鏈剖分那樣子的鏈)。
解題思路:很容易想到貪心,但是從上往下貪心不可取,但是我們知道每一個葉子節點必定屬於一條鏈,那麼那個葉子節點所在的鏈必定是越長越好。所以我們可以從下往上貪心,每個節點儘可能的走到他的最遠的能走到的父親那裡,這裡我們可以用樹上倍增處理。我們用top記錄每個節點最長能去到哪裡,然後從下往上遍歷每一個節點,每個節點貪心的取能去到的最遠的節點即可。整個過程很像樹鏈剖分,詳見程式碼。
#include <bits/stdc++.h>
using namespace std;
const int MAXN=100005;
typedef long long ll;
vector<int> G[MAXN];
int fa[25][MAXN];
int dep[MAXN];
int top[MAXN];//每個節點最遠能去到的那個點
int W[MAXN];
ll sum[MAXN];//從上往下走的節點值的和
int son[MAXN];//每個節點取的那個最遠能走到的那個點
ll N,L,S;
//倍增處理節點的第2^k個祖先是哪個
void initST(){
fa[0][1]=-1;
for(int k=0;k<20;k++)
for(int i=1;i<=N;i++)
if(fa[k][i]<0)
fa[k+1][i]=-1;
else
fa[k+1][i]=fa[k][fa[k][i]];
}
//求出top,sum陣列
void dfs1(int u,int pre){
sum[u]=sum[pre]+W[u];
dep[u]=dep[pre]+1;
top[u]=u;
//倍增找能去到的最遠的點
int dis=L;
for(int k=20;k>=0;k--){
int f=fa[k][top[u]];
if((1<<k)>=dis||f==-1)//判斷L是否滿足
continue;
if(sum[u]-sum[f]+W[f]>S)//判斷S是否滿足
continue;
dis-=(1<<k);
top[u]=f;//更新
}
for(int i=0;i<G[u].size();i++)
dfs1(G[u][i],u);
}
//求出son陣列並記錄答案
int ans=0;
void dfs2(int u){
int best=-1;
for(int i=0;i<G[u].size();i++){
int v=G[u][i];
dfs2(v);
if(son[v]==v)//這個很重要,相當於把已經確定的鏈都刪掉
continue;
if(best==-1||dep[best]>dep[son[v]])//儲存最遠的那個
best=son[v];
}
if(best==-1){
best=top[u];
ans++;
}
son[u]=best;
}
int main(){
scanf("%lld%lld%lld",&N,&L,&S);
for(int i=1;i<=N;i++){
scanf("%lld",&W[i]);
if(W[i]>S)
{
cout<<-1<<endl;
return 0;
}
}
int tmp;
for(int i=2;i<=N;i++)
{
scanf("%d",&tmp);
fa[0][i]=tmp;
G[tmp].push_back(i);
}
initST();
dfs1(1,0);
dfs2(1);
cout<<ans<<endl;
return 0;
}