gym102759D Just Meeting XXI Open Cup. Grand Prix of Korea

https://codeforces.com/gym/102759/problem/D

自闭场,4题跑路,五点共圆和uestc 7题也太猛了

由于要满足不存在一个3元环中,一条边同时小于另外两条边,那么对于u-v这条边,如果u-w,w-v只有一边存在,那么另一边可以任意变的边就等于这两条边的较小值,如果两条边都不存在,就直接2条1就行了

然后可以发现对于同一个联通分量两个点之间的边一定是确定的,且对于这个联通分量的最大或最小生成树,那些还无法确定的边等于这棵树上两个点路径上最小的那条边最优,而对于那些没有加入这棵生成树中的确定边,必须等于这条路径上的最小边,否则非法直接-1。

那么就直接全局跑最大生成树,从大到小枚举边,那么对于u,v所在的x,y连通块,他们之间所有点对的路径最小值就是这条边,所以ans+=sz[u]*sz[v]*e.l然后再把这两个连通块合并

每个连通块之间的点对就直接全是1了,全加上就行了

最后对每个连通块跑dfs后倍增lca判断那些没加入的边是否等于他两个端点之间路径上的最小值判下合不合法

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;

const int maxl=3e5+10;

int n,m;ll ans;
int f[maxl],dep[maxl];ll sz[maxl];
int fa[21][maxl],mi[21][maxl];
struct edge
{
	int u,v,f;ll val;
}b[maxl];
struct ed{int v;ll l;};
vector<ed> e[maxl];

inline bool cmp(const edge &a,const edge &b)
{
	return a.val>b.val;
}

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

inline void prework()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++)
		scanf("%d%d%lld",&b[i].u,&b[i].v,&b[i].val),b[i].f=0;
	sort(b+1,b+1+m,cmp);
	for(int i=1;i<=n;i++)
		f[i]=i,sz[i]=1;
	ans=0;int x,y;
	for(int i=1;i<=m;i++)
	{
		x=find(b[i].u);y=find(b[i].v);
		if(x!=y)
		{
			b[i].f=1;
			ans+=sz[x]*sz[y]*b[i].val;
			f[y]=x;sz[x]+=sz[y];
			e[b[i].u].push_back(ed{b[i].v,b[i].val});
			e[b[i].v].push_back(ed{b[i].u,b[i].val});
		}
	}
}

inline void dfs(int u,int f,int l)
{
	fa[0][u]=f;mi[0][u]=l;dep[u]=dep[f]+1;
	for(ed ee:e[u])
	if(ee.v!=f)
		dfs(ee.v,u,ee.l);
}

inline int milca(int u,int v)
{
	int ret=b[1].val;
	if(dep[u]<dep[v])
		swap(u,v);
	for(int k=20;k>=0;k--)
	if((dep[u]-dep[v])>>k&1)
	{
		ret=min(ret,mi[k][u]);
		u=fa[k][u];
	}
	if(u==v)
		return ret;
	for(int k=20;k>=0;k--)
	if(fa[k][u]!=fa[k][v])
	{
		ret=min(ret,mi[k][u]);
		ret=min(ret,mi[k][v]);
		u=fa[k][u];v=fa[k][v];
	}
	ret=min(ret,mi[0][u]);ret=min(ret,mi[0][v]);
	return ret;
}

inline void mainwork()
{
	ll sum=0;
	for(int i=1;i<=n;i++)
	if(f[i]==i)
	{
		sum+=sz[i]*(n-sz[i]);
		for(ed ee:e[i])
			dfs(ee.v,i,ee.l);
	}
	for(int k=1;k<=20;k++)
		for(int i=1;i<=n;i++)
		{
			fa[k][i]=fa[k-1][fa[k-1][i]];
			mi[k][i]=min(mi[k-1][i],mi[k-1][fa[k-1][i]]);
		}
	for(int i=1;i<=m;i++)
	if(!b[i].f)
	{
		if(b[i].val!=milca(b[i].u,b[i].v))
		{
			ans=-1;
			return;
		}
	}
	ans+=sum/2;
}

inline void print()
{
	printf("%lld\n",ans);
}

int main()
{
	prework();
	mainwork();
	print();
	return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值