2024牛客暑期多校训练营6 A Cake
题目大意
详细题目传送门
\(A\) 和 \(B\) 要从轮流走,从根到一个叶子节点位置,\(A\) 先。树有边权 \(0,1\) ,按照顺序经过的边权按字符串拼接得到一个串 \(S\)。现在 \(B\)可以把 \(1\) 拆分成任意个分数(但不能超过 \(S\) 的长度,且分数可以为空,)两人按照 \(S\) 串的顺序选取,如果 \(S_i=0\) 则 \(B\) 拿, \(S_i=1\) 则 \(A\) 拿。问 \(A\) 最多能拿多少。
\(2\leq n\leq 2\times 10^5\),\(n\) 为数的大小。
思路
首先先考虑第二部分。发现对于后手来说是十分被动的,对于能拿的希望拿的多一点,但最终能拿多少全部取决于先手。发现双方的利益取悦于找到一个 \(l\) 使得平均分成 \(l\) 份让其中的占比最大。则我们维护树上每一个节点边的最优前缀表示其中走到这里选 \(0\) 和选 \(1\) 的期望占比。其中 \(0\) 要取 \(\max\),\(1\) 要取 \(\min\),因为第二部分的选择权是 \(0\)。
之后就可以考虑博弈,从叶子节点反推,每一次取当前手的子节点对自己最优的,然后最后输出根节点的值即可。
代码
#include <bits/stdc++.h>
using namespace std;
const int NR = 200001;
int n;
vector<pair<int, int> > e[NR];
int cnt[2][NR];
double f[2][NR];
void dfs1(int u, int fa, int m){
if (m > 0){
f[0][u] = 1.0 * cnt[0][u] / (1.0 * m);
f[1][u] = 1.0 * cnt[1][u] / (1.0 * m);
}
if (m > 1){
f[0][u] = max(f[0][u], f[0][fa]);
f[1][u] = min(f[1][u], f[1][fa]);
}
//cout<<u<<" "<<f[0][u]<<" "<<f[1][u]<<endl;
for (int i = 0; i < e[u].size(); i++){
int v = e[u][i].first, w = e[u][i].second;
if (v != fa){
cnt[0][v] = cnt[0][u];
cnt[1][v] = cnt[1][u];
cnt[w][v]++;
dfs1(v, u, m + 1);
}
}
}
void dfs2(int u, int fa, bool flag){
double maxn0 = -1, maxn1 = -1;
int id = 0;
bool leaf=true;
for (int i = 0; i < e[u].size(); i++){
int v = e[u][i].first;
if (v != fa){
dfs2(v, u, (flag ^ 1));
leaf=false;
if (flag){
if(f[1][v]>maxn1){
maxn1=f[1][v];
maxn0=f[0][v];
}
}
else{
if(f[0][v]>maxn0){
maxn0=f[0][v];
maxn1=f[1][v];
}
}
}
}
if(!leaf){
f[0][u]=maxn0;
f[1][u]=maxn1;
}
//cout<<u<<" "<<f[0][u]<<" "<<f[1][u]<<endl;
}
void test(){
cin >> n;
for (int i = 1; i <= n; i++){
e[i].clear();
f[0][i]=f[1][i]=0;
cnt[0][i]=cnt[1][i]=0;
}
for (int i = 1; i < n; i++){
int u, v, w;
cin>>u>>v>>w;
e[u].push_back(make_pair(v, w));
e[v].push_back(make_pair(u, w));
}
dfs1(1, 0, 0);
dfs2(1, 0, true);
printf("%0.12lf\n",f[1][1]);
}
int main(){
int T;
cin >> T;
while (T--){
test();
}
return 0;
}