【CF1496F】Power Sockets

题目

题目链接: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;
}
原文地址:https://www.cnblogs.com/stoorz/p/15250548.html