道路费用[APIO2013][最小生成树]

文章目录

题目

Luogu
题目大意:
n n n 个点 m m m 条边无向连通图,有些边是你的可以收费,建立最小生成树方式你定,问最大获利
k ≤ 20 k\le 20 k20

思路

由于收费边集很小
首先把所有收费边加入集合建立最小生成树,去掉收费边剩下构成了 k + 1 k+1 k+1 个连通块,进行缩点,然后枚举收费边集跑 K r u s k a l Kruskal Kruskal ,那么对于一条边的最大收费可以利用破圈思想进行取 m i n min min 更新,这里由于点很少,暴力爬链
时间复杂度 O ( m l o g m + 2 k k 2 ) O(mlogm+2^kk^2) O(mlogm+2kk2)

代码

#include<set>  
#include<map>  
#include<stack>  
#include<cmath>  
#include<cstdio>  
#include<queue>  
#include<vector>  
#include<cstring> 
#include<climits>  
#include<iostream> 
#include<algorithm> 
using namespace std;
#define LL long long
int read(){
    int f=1,x=0;char c=getchar();
    while(c<'0'||'9'<c){if(c=='-')f=-1;c=getchar();} 
    while('0'<=c&&c<='9'){x=x*10+c-'0';c=getchar();} 
    return f*x;
}	
#define fi first
#define se second
#define mp make_pair
const int MAXK=20;
const int MAXN=300000;
const int INF=0x3f3f3f3f;
typedef pair<int,int> pii;
struct Edge{
	int u,v,w;
	friend bool operator < (Edge a,Edge b){return a.w<b.w;}
}edge1[MAXN+5],edge3[MAXK*MAXK+5];
pii edge2[MAXN+5];
bool cho[MAXN+5],mark[MAXK+5];
LL val[MAXN+5],ans,sum[MAXN+5];
int n,m,k,pre[MAXN+5],tp,icnt,id[MAXK+5];
int rt,mn[MAXN+5],dep[MAXN+5],fa[MAXN+5];
struct E{
	int v,nxt;
}edge[MAXK*MAXK+5];
int ecnt,head[MAXN+5];
void Addedge(int u,int v){
	edge[++ecnt]=(E){v,head[u]},head[u]=ecnt;
	edge[++ecnt]=(E){u,head[v]},head[v]=ecnt;
	return ;
}
int Find(int u){return pre[u]==u?u:(pre[u]=Find(pre[u]));}
void DFS(int u){
	sum[u]=val[u];
	for(int i=head[u];i;i=edge[i].nxt){
		int v=edge[i].v;
		if(v==fa[u]) continue;
		fa[v]=u,dep[v]=dep[u]+1,DFS(v),sum[u]+=sum[v];
	}
	return ;
}
void Kruskal(){
	ecnt=0;
	for(int i=1;i<=k+1;i++)
		head[id[i]]=0,fa[id[i]]=0,dep[id[i]]=0,mn[id[i]]=INF,pre[id[i]]=id[i];
	for(int i=1;i<=k;i++)
		if(mark[i]){
			int u=Find(edge2[i].fi),v=Find(edge2[i].se);
			if(u==v) return ;
			pre[u]=v;
			Addedge(edge2[i].fi,edge2[i].se);
		}
	for(int i=1;i<=k;i++){
		int u=Find(edge3[i].u),v=Find(edge3[i].v);
		if(u==v) continue;
		pre[u]=v;
		Addedge(edge3[i].u,edge3[i].v);
	}
	DFS(rt);
	for(int i=1;i<=k;i++){
		int u=edge3[i].u,v=edge3[i].v,w=edge3[i].w;
		if(dep[u]<dep[v]) swap(u,v);
		while(dep[u]!=dep[v]) mn[u]=min(mn[u],w),u=fa[u];
		while(u!=v){
			mn[u]=min(mn[u],w),u=fa[u];
			mn[v]=min(mn[v],w),v=fa[v];
		}
	}
	LL ret=0;
	for(int i=1;i<=k;i++)
		if(mark[i]){
			int u=edge2[i].fi,v=edge2[i].se;
			if(dep[u]<dep[v]) swap(u,v);
			ret+=mn[u]*sum[u];
		}
	ans=max(ans,ret);
	return ;
}
int main(){
	n=read(),m=read(),k=read();
	for(int i=1;i<=m;i++)
		edge1[i].u=read(),edge1[i].v=read(),edge1[i].w=read();
	for(int i=1;i<=k;i++)
		edge2[i].fi=read(),edge2[i].se=read();
	for(int i=1;i<=n;i++)
		val[i]=read();
	sort(edge1+1,edge1+m+1);
	for(int i=1;i<=n;i++)
		pre[i]=i;
	for(int i=1;i<=k;i++)
		pre[Find(edge2[i].fi)]=Find(edge2[i].se);
	for(int i=1;i<=m;i++){
		int u=Find(edge1[i].u),v=Find(edge1[i].v);
		if(u==v) continue;
		cho[i]=1,pre[u]=v;
	}
	for(int i=1;i<=n;i++)
		pre[i]=i;
	for(int i=1;i<=m;i++)
		if(cho[i])
			pre[Find(edge1[i].u)]=Find(edge1[i].v);
	rt=Find(1);
	for(int i=1,t;i<=n;i++)
		if((t=Find(i))!=i)
			val[t]+=val[i];
		else id[++icnt]=i;
	for(int i=1;i<=m;i++)
		edge1[i].u=Find(edge1[i].u),edge1[i].v=Find(edge1[i].v);
	for(int i=1;i<=k;i++)
		edge2[i].fi=Find(edge2[i].fi),edge2[i].se=Find(edge2[i].se);
	for(int i=1;i<=m;i++){
		int u=Find(edge1[i].u),v=Find(edge1[i].v);
		if(u==v) continue;
		edge3[++tp]=edge1[i];
		pre[u]=v;
	}
	for(int s=1;s<(1<<k);s++){
		for(int i=1;i<=k;i++)
			if((s>>(i-1))&1)
				mark[i]=1;
			else mark[i]=0;
		Kruskal();
	}
	printf("%lld\n",ans);
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值