CF1900 紫名选手竟在 OI 赛制的 NOI 模拟赛切掉了 CF 3300*?
chenxi2009 · · 题解
思路
OI 赛制模拟赛把这个切了,遂写一篇题解“昭告天下”。
这是一个没有特殊定根、没有反悔贪心的做法。
如果一个点的度数是
从上往下做显然不可行,考虑从下往上,如果儿子的度数是
如上图,围绕
仅此而已吗?
并非。注意到如果一个儿子给父亲传上来了一条路径,另一个儿子也给父亲传上来了一条路径,那么这两条路径可以在父亲处接在一起,节省了一条路径。
如上图,
这里有两点需要注意:
- 刚刚的
1 号点初始需求为1 ,但是我们完成它的需求却造成了-2 的贡献,这是因为完成需求只需要一条上传路径,而我们另一边的儿子还可以再上传一条路径进行拼凑。注意每个儿子上传用来进行此类拼凑的路径数量是有限的,上限是前面提到的那个y-1 。这样的拼凑消耗一条上传路径就可以造成-1 的贡献,贡献比为1 。 - 若消耗两个儿子上传的边各一条进行原先需求之外的拼凑,则需要消耗共两条上传路径才能造成
-1 的贡献,贡献比是0.5 。这样的拼凑没有数量上限,但是要求拼凑的两条上传路径不来自同一个子树(来自同一个子树的话就会有相同的边了)。
我们先考虑第二类拼凑的情况,发现有的时候并不是所有上传路径都能用来拼凑的。比如说如果上面那个例子中
什么情况下会产生限制路径呢?显然是有一个儿子上传的路径数量超过总上传路径数量的一半的时候,设总上传路径数为
接着考虑第一类拼凑,度数为
等父亲的需求被填满之后,儿子就可以随便进行第二类的拼凑了...吗?
还没完。我们已经知道第一类的拼凑更优了,那么我们还可以拿走儿子剩下来的更多边,传给父亲的父亲,父亲的父亲的父亲...用来完成第一类。这么说被父亲剩下来的路径也不能直接用来做第二类,还要继续保存着。
似乎感觉很难实现,实际上我们只要从下往上做,记录下层有多少个自由路径和限制路径传上来即可,随后一层一层处理,注意下层的限制路径经过上传之后可能变成自由路径(可以和父亲的父亲的...的父亲的另外一个子树的一条路径合并),递归到根之后再把剩下来的自由路径两两配对即可。正确性显然。
好像很对的样子为什么我考场上会这么忐忑呢时间复杂度
代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 600000;
int T,n,u,v;
long long ans;
vector<int>e[N];
pair<ll,ll> sch(int u,int fa){//返回值 first 是 u 子树内的自由路径条数,second 是限制路径条数
ll rd = 0,mx = 0,sum1 = 0,sum2 = 0,mxf = 0;
//rd 记录儿子子树被用作满足 u 的需求的路径条数,sum1 记录自由路径条数,sum2 记录限制路径条数
//mx 记录上传限制路径最多的子树,mxf 记录和 mx 来自同一子树的自由路径数量,mxf 不能和 mx 配对
ans += 1ll * e[u].size() * (e[u].size() - 1) / 2;//u 的原始需求路径
for(auto v : e[u]){
if(v == fa) continue;
auto [a,b] = sch(v,u);
if(b < e[u].size()){//限制路径条数不足以满足 u 对 v 度数减一的需求
if(a + b < e[u].size()) rd += a + b;//限制路径和自由路径加起来都不够,只能全部丢去满足需求了
else{//限制路径和自由路径加起来足够满足需求,剩余部分的自由路径
rd += e[u].size() - 1;
sum1 += a - (e[u].size() - 1 - b);//剩下这么多自由路径
}
}
else{
sum1 += a,rd += e[u].size() - 1,sum2 += b - (e[u].size() - 1);//自由路径全部剩下,限制路径剩下一部分
if(b - (e[u].size() - 1) > mx) mx = b - (e[u].size() - 1),mxf = a;//记录限制路径最多的子树
}
}
ans -= rd;//满足需求消耗的路径条数
sum2 += sum1 - mxf,sum1 = mxf;//把可以配对的自由路径丢到限制路径里,一起尝试与最多的限制路径配对将其转化为自由路径
if(mx > sum2 - mx) sum1 += 2 * (sum2 - mx),sum2 -= 2 * (sum2 - mx);//还是没有办法全部消灭,只能配出 sum2-mx 对
else sum1 += sum2,sum2 = 0;//可以全部配对,全部转化为自由路径
return {sum1,sum2 + e[u].size() - 1};//额外有度数减一条限制路径,来自于 u 的原始需求,别忘了本来就有度数减一条上传路径
}
int main(){
cin.tie(0)->sync_with_stdio(0);
cin >> T;
while(T --){
ans = 0;
cin >> n;
for(int i = 1;i <= n;i ++) e[i].clear();
for(int i = 1;i < n;i ++){
cin >> u >> v;
e[u].emplace_back(v),e[v].emplace_back(u);
}
auto [a,b] = sch(1,0);
printf("%lld\n",ans - a / 2);//自由路径两两配对
}
return 0;
}
招笑赛时代码