【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 \times 10^5,2 \leq k \leq 10^9,3 \le l_i \le 2 \times 10^5\)

思路

无解当且仅当 \(1+\sum^{n}_{i=1}(l_i-2)<k\)
如果我们已经使用了一些链,现在要加入第 \(i\) 条链,很显然最优操作一定是取第 \(\lfloor\frac{l_i}{2}\rfloor\) 个点,挂在深度最小的白色点下面。
所以只需要考虑最优的加链的顺序。
如果先后加了 \(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(n\log 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;
}
posted @ 2021-09-10 11:14  stoorz  阅读(58)  评论(0编辑  收藏  举报