题目
题目链接:https://codeforces.com/problemset/problem/1469/F
初始给出 (n) 条长度分别为 (l_1,l_2,dots,l_n) 的链,以及一棵只有一个根节点的树,所有点初始都为白色。
每次操作可以用一条边将一条链中的一个点 (u) 和树上某个白点 (v) 连接起来,(u,v) 都变黑。一条链至多操作一次,也可以不操作。
操作完后树的权值为根节点到第 (k) 近的白点的距离。问操作方案所得树中最小的权值。若无法得到 (k) 个白点,则输出 (-1)。
(1 leq n leq 2 imes 10^5,2 leq k leq 10^9,3 le l_i le 2 imes 10^5)。
思路
无解当且仅当 (1+sum^{n}_{i=1}(l_i-2)<k)。
如果我们已经使用了一些链,现在要加入第 (i) 条链,很显然最优操作一定是取第 (lfloorfrac{l_i}{2}
floor) 个点,挂在深度最小的白色点下面。
所以只需要考虑最优的加链的顺序。
如果先后加了 (i,j) 两条链且 (l_i<l_j),那么把 (i,j) 加的顺序取反一定不劣。因为这样白点数量相同,且深度都不增。
如果加了第 (i) 条链,没有加第 (j) 条链,且 (l_i<l_j),那么把第 (i) 条链替换成第 (j) 条链显然更优。
所以肯定是按照链长度从大到小依次加上去。
观察样例可以发现并不是加的越多越好。考虑什么时候停止加链。
假设一条链要挂在深度为 (d) 的点下方,那么相当于把深度为 (d) 的一个白色点删掉了,增加的白色点的深度是从 (d+2) 开始的。也就是说,如果深度不超过 (d+1) 的白色点数量不小于 (k) 了,那么此时就需要停止加链,因为不会再有深度不超过 (d+1) 的白点加入。反之答案一定不小于 (d+2),而加入这条链可以用一个深度不超过 (d+2) 的白点换两个深度不超过 (d+2) 的白点,所以一定会继续加。
那么用一个差分数组维护一下每个深度的白点数量就可以了。时间复杂度 (O(nlog n)),瓶颈在排序。
代码
#include <bits/stdc++.h>
#define pb push_back
#define mp make_pair
#define fi first
#define se second
using namespace std;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
const int N=1000010;
int n,m,dep,a[N],cnt[N];
ll sum;
int find()
{
while (!cnt[dep]) dep++;
return dep;
}
int main()
{
scanf("%d%d",&n,&m);
for (int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
sum+=a[i]-2;
}
if (sum+1<m) { cout<<"-1"; return 0; }
sort(a+1,a+1+n);
dep=1; cnt[1]=1; cnt[2]=-1;
for (int i=n;i>=1;i--)
{
int d=find();
cnt[d]--; cnt[d+1]++;
cnt[d+2]++; cnt[d+(a[i]+1)/2+1]--;
cnt[d+2]++; cnt[d+a[i]/2+2]--;
d=find();
if (cnt[d]*2+cnt[d+1]>=m) break;
}
for (int i=1;i<m;i++)
{
int d=find();
cnt[d]--; cnt[d+1]++;
}
cout<<find()-1;
return 0;
}