atcoder 2019.1.27晚 E.Weights on Vertices and Edges

https://atcoder.jp/contests/nikkei2019-qual/tasks/nikkei2019_qual_e

题目给出n个点m条边,问你至少删掉多少条边,能够使得最后的所有连通块中,一个连通块中的点权之和要大于这个连通块中每一条边的值。

考场上已经接近正解了,按边权排序,从小到大考虑是否加进答案里去,并查集维护一下点权和,求出最多能加进答案的边,输出m-ans,但是不知道这个怎么判断。

题解的思路对这个判断很巧妙,对于一个合法连通块中的边,因为从小到大枚举的边,并查集中维护一个当前连通块中还有多少边没有加进答案,如果此时点权和已经比当前边大了,那么说明该连通块剩下的边全部合法,于是把当前连通块中没加入答案的边全部加入答案。

在submissions中还发现另外一种写法,用非路径压缩的最短路写,每次把小的连通块的根连上大的连通块的根,这样分叉就很多,也许能保证找根的深度不超过logn?(没想的很清楚)

从头开始跑一遍最小生成树,记录并查集上连接的顺序情况,而且不清空之前的值,再从后往前删掉那些非法的边,按照记录还原点权和,连通块大小等信息,最后剩下的边就是最后连通性地状况,再扫一遍所有边,在不改变连通性地情况下,看哪些边在答案中。

前一种正解52ms,后一种56ms,非压缩路径的并查集也这么快的吗?

#include<bits/stdc++.h>
#define maxl 100010

using namespace std;

int n,m,ans;
int a[maxl];
struct ed
{
	int u,v,val;
}e[maxl];
int f[maxl],cnt[maxl];
long long sum[maxl];

inline bool cmp(const ed &x,const ed &y)
{
	return x.val<y.val;
}

inline void prework()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
		f[i]=i;cnt[i]=0;sum[i]=a[i];
	}
	for(int i=1;i<=m;i++)
		scanf("%d%d%d",&e[i].u,&e[i].v,&e[i].val);
	sort(e+1,e+1+m,cmp);
}

inline int find(int x)
{
	if(f[x]!=x)
	{
		f[x]=find(f[x]);
		cnt[f[x]]+=cnt[x];
		sum[f[x]]+=cnt[x];
		sum[x]=cnt[x]=0;
	}
	return f[x];
}

inline void mainwork()
{
	int x,y,xx,yy;
	for(int i=1;i<=m;i++)
	{
		x=e[i].u;y=e[i].v;
		xx=find(x);yy=find(y);
		if(xx!=yy)
		{
			cnt[xx]+=cnt[yy]+1;
			sum[xx]+=sum[yy];
			f[yy]=xx;
			sum[yy]=0;cnt[yy]=0;
			if(sum[xx]>=e[i].val)
			{
				ans+=cnt[xx];
				cnt[xx]=0;
			}
		}
		else
		{
			cnt[xx]++;
			if(sum[xx]>=e[i].val)
			{
				ans+=cnt[xx];
				cnt[xx]=0; 
			}
		}
	}
}

inline void print()
{
	printf("%d",m-ans);
}

int main()
{
	prework();
	mainwork();
	print();
	return 0;
}
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#define ll long long
using namespace std;
const int N=2e5;
struct edge{int fr,to,w;}a[N],sta[N];
int n,m,val[N],fa[N];
int siz[N],cnt,top,ans;
ll sum[N];
int cmp(const edge&A,const edge&B) {return A.w<B.w;}
int find(int x) {return fa[x]==x?x:find(fa[x]);}
int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&val[i]);
		fa[i]=i,siz[i]=1,sum[i]=val[i];
	}
	for(int i=1,x,y,w;i<=m;i++)
	{
		scanf("%d%d%d",&x,&y,&w);
		a[++cnt]=(edge){x,y,w};
	}
	sort(a+1,a+cnt+1,cmp);

	for(int i=1;i<=cnt;i++)
	{
		int x=a[i].fr,y=a[i].to;
		x=find(x);y=find(y);
		if(x==y) continue;
		if(siz[x]>siz[y]) swap(x,y);
		sta[++top]=(edge){x,y,a[i].w};
		fa[x]=y;siz[y]+=siz[x];sum[y]+=sum[x];
	}
	for(int i=top;i>=1;i--)
	{
		int x=sta[i].fr,y=sta[i].to;
		ll v=sum[find(y)],val=sta[i].w;
		if(v<val) fa[x]=x,siz[y]-=siz[x],sum[y]-=sum[x];
	}
	for(int i=1;i<=m;i++)
	{
		int x=a[i].fr,y=a[i].to;
		if(find(x)!=find(y)) ans++;
		else if(a[i].w>sum[find(x)]) ans++;
	}
	cout<<ans<<endl;
}

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值