https://codeforces.com/contest/1746/problem/D
题意
给定一个 n 个节点的有根树,根节点编号为 1。
对于每个节点有价值
s
[
i
]
s[i]
s[i]。
现在要选出 k 条简单路径,满足:
- 每条路径都从根节点 1 开始;
- 定义节点 i i i 一共被经过了 c i c_i ci 次,要满足对于任意的兄弟节点 ( u , v ) (u, v) (u,v), ∣ c u − c v ∣ ≤ 1 |c_u−c_v|≤1 ∣cu−cv∣≤1。
这 k 条简单路径的总价值定义为 ∑ i = 1 n c i ∗ s i \sum\limits_{i=1}^n c_i *s_i i=1∑nci∗si
问,如何挑选能够使得总价值最大?输出最大价值。
2
≤
n
≤
2
⋅
1
0
5
,
1
≤
k
≤
1
0
9
2 \le n \le 2 \cdot 10^5,\ 1 \le k \le 10^9
2≤n≤2⋅105, 1≤k≤109
0
≤
s
i
≤
1
0
4
0 \le s_i \le 10^4
0≤si≤104
思路
一共 k 条路径,那么根节点 1 就被经过了 k 次,为了让总价值最大,并且满足兄弟节点被经过次数之差不超过 1,假设根节点有 cnt 个儿子,那么每个儿子初始要被经过 k/cnt 次,还有 k%cnt 个儿子多经过一次。(场上连这个都没想到,只是看着样例画画,啥也没画出来,没有一点想法。。
根节点经过 k 次,那么所有节点至少被经过的次数就被确定了,假设为
k
i
k_i
ki 次。
假设一个节点 x 被经过了
k
i
k_i
ki 次,有
c
n
t
cnt
cnt 个儿子,那么所有儿子都至少经过了
k
i
/
c
n
t
k_i/cnt
ki/cnt 次,然后
k
i
%
c
n
t
k_i\%cnt
ki%cnt 个儿子多经过一次。
如何判断哪些儿子被多经过一次呢?
如果一个儿子多经过一次之后,相比于原来得到的增量最大,那么这个儿子要被多经过一次。
按照增量从大到小排序,取前
k
i
%
c
n
t
k_i\%cnt
ki%cnt 个,多经过一次。
所以要维护出来每个节点经过 k i k_i ki 次 和 再多经过一次 得到的最大价值。
定义 f[i][0]
表示,节点 i 经过
k
i
k_i
ki 次之后,以该节点为根的子树得到的最大价值;
f[i, 1]
表示,节点 i 经过
k
i
+
1
k_i + 1
ki+1 次之后,以该节点为根的子树得到的最大价值。
节点 x 经过
k
i
k_i
ki 次: f[x, 0]
,从增量最大的
k
i
%
c
n
t
k_i\%cnt
ki%cnt 个儿子的 f[tx, 1]
转移,剩下的从 f[tx, 0]
来转移;
for(int i=1;i<=idx;i++) //所有的儿子节点a[i]
{
int tx = a[i];
if(i <= left) ans[x][0] += ans[tx][1];
else ans[x][0] += ans[tx][0];
}
节点 x 经过
k
i
+
1
k_i + 1
ki+1 次: f[x, 1]
,注意要从增量最大的
k
i
%
c
n
t
+
1
k_i\%cnt + 1
ki%cnt+1 个儿子的 f[tx, 1]
转移,而不是从最大的
(
k
i
+
1
)
%
c
n
t
(k_i+1)\%cnt
(ki+1)%cnt 个儿子,因为儿子至少经过的次数是
k
i
k_i
ki,不是
k
i
+
1
k_i+1
ki+1,这里只是额外多经过一次,也就是取模之后剩余的多了一个。然后剩下的儿子从 f[tx, 0]
来转移。
for(int i=1;i<=idx;i++)
{
int tx = a[i];
if(i <= left + 1) ans[x][1] += ans[tx][1];
else ans[x][1] += ans[tx][0];
}
最后的答案就是根节点经过 k 次的最大价值 f[1, 0]
。
Code
#include<bits/stdc++.h>
using namespace std;
#define Ios ios::sync_with_stdio(false),cin.tie(0)
#define int long long
#define fi first
#define se second
const int N = 200010, mod = 1e9+7;
int T, n, m;
int a[N], c[N];
int ans[N][2];
vector<int> e[N];
int k;
void init(){
for(int i=1;i<=n;i++) e[i].clear();
}
bool cmp(int x, int y){
return ans[x][1] -ans[x][0] > ans[y][1] - ans[y][0];
}
void dfs(int x, int sum)
{
ans[x][0] = sum * c[x];
ans[x][1] = (sum + 1) * c[x];
int cnt = e[x].size();
for(int tx : e[x])
{
dfs(tx, sum / cnt);
}
int idx = 0;
for(int tx : e[x]) a[++idx] = tx;
sort(a+1, a+idx+1, cmp); //根据答案增量排序,选增量最大的sum%cnt个儿子多经过一次。
if(cnt)
{
int left = sum % cnt;
for(int i=1;i<=idx;i++)
{
int tx = a[i];
if(i <= left) ans[x][0] += ans[tx][1];
else ans[x][0] += ans[tx][0];
if(i <= left + 1) ans[x][1] += ans[tx][1];
else ans[x][1] += ans[tx][0];
}
}
}
signed main(){
Ios;
cin >> T;
while(T--)
{
cin >> n >> k;
init();
for(int i=2;i<=n;i++)
{
int x; cin >> x;
e[x].push_back(i);
}
for(int i=1;i<=n;i++) cin >> c[i];
dfs(1, k);
cout << ans[1][0] << endl;
}
return 0;
}
还是太菜了。。