[CF1515F]Phoenix and Earthquake

Phoenix and Earthquake

题解

挺水的一道题。

首先我们要想明白如何判断一个图是否能行。
首先,如果不是联通图或者 ∑ i = 1 n a i < ( n − 1 ) x \sum_{i=1}^{n}a_{i}< (n-1)x i=1nai<(n1)x是肯定不行的,因为它合并都合并不了 n − 1 n-1 n1条边。
否则,它是一定能行的。因为这样必定有一条边的两端和是大于 x x x,那么这条边就一定可以被合并掉。然后,我们得到的是一个 n − 1 n-1 n1个点,且 ∑ i = 1 n a i ⩾ ( n − 2 ) x \sum_{i=1}^{n}a_{i}\geqslant (n-2)x i=1nai(n2)x的图,这个图依旧可以重复以下过程。

证明了可行性,接下来我们操作呢?
首先,如果有一个权值不小于 x x x的点,我们可以直接将它与其它点合并,它与任意一个点合并都是可行的。
否则我们可以证明现当每个点都小于 x x x时,每一条边的和都是不小于 x x x的。
如果有一条边满足的 a u + a v < x a_{u}+a_{v}< x au+av<x,则其它 n − 2 n-2 n2个点都取 x − 1 x-1 x1,总和也达不到 ( n − 1 ) x (n-1)x (n1)x,也就是不满足我们最开始的条件,这当然是不可能的。
所以这样我们只需要随便合并一条边就可以了。
具体实现过程中,我们只需要每次对权值最大的点找一条边合并即可。

时间复杂度 O ( m l o g   n ) O\left(mlog\,n\right) O(mlogn)

源码

#include<bits/stdc++.h>
using namespace std;
#define MAXN 300005
#define lowbit(x) (x&-x)
#define reg register
#define mp make_pair
#define fir first
#define sec second
typedef long long LL;
typedef unsigned long long uLL;
typedef unsigned int uint;
const int INF=0x7f7f7f7f;
const int jzm=233;
const int mo=998244353;
const double Pi=acos(-1.0);
typedef pair<int,int> pii;
const double PI=acos(-1.0);
template<typename _T>
_T Fabs(_T x){return x<0?-x:x;}
template<typename _T>
void read(_T &x){
	_T f=1;x=0;char s=getchar();
	while(s>'9'||s<'0'){if(s=='-')f=-1;s=getchar();}
	while('0'<=s&&s<='9'){x=(x<<3)+(x<<1)+(s^48);s=getchar();}
	x*=f;
}
int n,m,head[MAXN],tot,fa[MAXN],last[MAXN];LL val[MAXN],sum,x;
struct edge{int to,nxt;}e[MAXN<<1];
void addEdge(int u,int v){e[++tot]=(edge){v,head[u]};head[u]=tot;if(!last[u])last[u]=tot;}
void makeSet(int x){for(int i=1;i<=x;i++)fa[i]=i;}
int findSet(int x){return x==fa[x]?x:fa[x]=findSet(fa[x]);}
void unionSet(int a,int b){int u=findSet(a),v=findSet(b);if(u!=v)fa[u]=v;}
struct ming{LL val;int id;friend bool operator < (const ming &x,const ming &y){return x.val<y.val;}};
priority_queue<ming> q;
int main(){
	read(n);read(m);read(x);makeSet(n);
	for(int i=1;i<=n;i++)read(val[i]),sum+=val[i];
	for(int i=1,u,v;i<=m;i++)read(u),read(v),addEdge(u,v),addEdge(v,u);
	if(sum<1ll*(n-1)*x){puts("NO");return 0;}puts("YES");
	for(int i=1;i<=n;i++)q.push((ming){val[i],i});sum=0;
	while(!q.empty()&&sum<n-1){
		int t=q.top().id;q.pop();if(t!=findSet(t))continue;
		//printf("match %d\n",t);
		for(int i=head[t];i;i=e[i].nxt){
			int v=findSet(e[i].to);head[t]=e[i].nxt;if(t==v)continue;
			val[t]=val[t]+val[v]-x;val[v]=0;fa[v]=t;q.push((ming){val[t],t});
			printf("%d\n",(i+1)/2);(i==last[t]?head[t]:e[last[t]].nxt)=head[v];last[t]=last[v];sum++;break;
		}
	}
	puts("");
	return 0;
}
 

谢谢!!!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值