BZOJ 3206 APIO2013 道路费用

82 篇文章 0 订阅
9 篇文章 0 订阅

Problem

BZOJ

Solution

注意到k只有20,那么对于这个图,很多点是由普通边连接的,那么我们可以先强制把所有的特殊边加入,然后把普通边做一次mst,则其他未加入的边肯定是不会做贡献的,把这些由普通边连起来的点缩在一起的,不妨称为新图。

新图只有 t o t ( t o t ≤ k + 1 ) tot(tot\leq k+1) tot(totk+1)个点,但是由于特殊边的选择方案可能导致树的形态发生改变,所以我们就枚举这k条边选择的方案,然后如果成环就直接continue,如果还未联通就再加入普通边,因此我们需要在新图上再做一遍普通边的mst。

然后由于这个图是最小生成树,所以对于一条特殊边连接了x,y,那么它的费用必须小于等于所有可以覆盖x,y的普通边的费用,而这些边只可能是新图普通边的mst做贡献。为什么?考虑在新图上做kruskal的过程,每条特殊边都肯定会被最小的一条边覆盖到。所以做多次路径覆盖即可。

时间复杂度 O ( m log ⁡ m + 2 k k 2 ) O(m\log m+2^k k^2) O(mlogm+2kk2),这个上界比较松,所以稍微注意点常数是可以通过的。如果你追求正确的复杂度,20个点的树上做树剖/倍增了解一下?

Code

#include <algorithm>
#include <cstring>
#include <cstdio>
#define rg register
using namespace std;
typedef long long ll;
const int maxn=100010,maxm=300010;
template <typename Tp> inline int getmin(Tp &x,Tp y){return y<x?x=y,1:0;}
template <typename Tp> inline int getmax(Tp &x,Tp y){return y>x?x=y,1:0;}
template <typename Tp> inline void read(Tp &x)
{
    x=0;int f=0;char ch=getchar();
    while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
    if(ch=='-') f=1,ch=getchar();
    while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
    if(f) x=-x;
}
struct data{int v,nxt;}edge[60];
struct Edge{
	int u,v,w;
	bool operator < (const Edge &t)const{return w<t.w;}
}a[maxm<<1],b[30],c[30];
struct UFS{
	int f[maxn];
	void clear(int n){for(rg int i=1;i<=n;i++) f[i]=i;}
	inline int find(int x){return f[x]==x?x:f[x]=find(f[x]);}
	int merge(int x,int y)
	{
		int fx=find(x),fy=find(y);
		if(fx==fy) return 0;
		return f[fy]=fx;
	}
}GS,NS,TS;
int n,m,k,p,tot,lim,head[30],f[30],dep[30],mx[30],us[30],vis[maxn];
ll ans,num[30],sz[30];
inline void insert(int u,int v)
{
	edge[++p]=(data){v,head[u]};head[u]=p;
	edge[++p]=(data){u,head[v]};head[v]=p;
}
void input()
{
	read(n);read(m);read(k);lim=1<<k;
	for(rg int i=1;i<=m;i++){read(a[i].u);read(a[i].v);read(a[i].w);}
	for(rg int i=1;i<=k;i++){read(b[i].u);read(b[i].v);}
}
void kruskal()
{
	int x,tmp=0;
	sort(a+1,a+m+1);
	GS.clear(n);NS.clear(n);
	for(rg int i=1;i<=k;i++) GS.merge(b[i].u,b[i].v);
	for(rg int i=1;i<=m;i++)
	  if(GS.merge(a[i].u,a[i].v))
	  	NS.merge(a[i].u,a[i].v);
	for(rg int i=1;i<=n;i++)
	  if(!vis[NS.find(i)]) vis[NS.find(i)]=++tot;
	for(rg int i=1;i<=k;i++)
	  b[i].u=vis[NS.find(b[i].u)],b[i].v=vis[NS.find(b[i].v)];
	for(rg int i=1;i<=n;i++){read(x);num[vis[NS.find(i)]]+=x;}
	for(rg int i=1;i<=n;i++) TS.f[i]=NS.f[i];
	for(rg int i=1;i<=m;i++)
	  if(NS.find(a[i].u)^NS.find(a[i].v))
	  {
	  	c[++tmp].w=a[i].w;
		c[tmp].u=vis[TS.find(a[i].u)];c[tmp].v=vis[TS.find(a[i].v)];
		NS.merge(a[i].u,a[i].v);
		if(tmp==tot-1) break;
	  }
}
void dfs(int x)
{
	dep[x]=dep[f[x]]+1;
	for(int i=head[x];i;i=edge[i].nxt)
	  if(edge[i].v^f[x])
	  {
	  	f[edge[i].v]=x;
	  	dfs(edge[i].v);
	  }
}
ll dp(int x)
{
	ll res=0ll;sz[x]=num[x];
	for(int i=head[x];i;i=edge[i].nxt)
	  if(edge[i].v^f[x])
	  {
	  	res+=dp(edge[i].v);
	  	sz[x]+=sz[edge[i].v];
	  }
	return res+(us[x]?sz[x]*mx[x]:0ll);
}
void color(int x,int y,int w)
{
	if(dep[x]<dep[y]) swap(x,y);
	while(dep[x]>dep[y]){getmin(mx[x],w);x=f[x];}
	while(x^y){getmin(mx[x],w);x=f[x];getmin(mx[y],w),y=f[y];}
}
int main()
{
	#ifndef ONLINE_JUDGE
	freopen("in.txt","r",stdin);
	#endif
	int cnt;
	input();
	kruskal();
	for(rg int s=1;s<lim;s++)
	{
		cnt=p=0;TS.clear(tot);
		memset(head,0,sizeof(head));
		memset(mx,0x3f,sizeof(mx));memset(us,0,sizeof(us));
		for(int i=1;i<=k;i++)
		  if(s&(1<<i-1))
		  {
			if(!TS.merge(b[i].u,b[i].v)){cnt=-1;break;}
			else{++cnt;insert(b[i].u,b[i].v);}
		  }
		if(cnt==-1) continue;
		for(int i=1;i<tot&&cnt<tot-1;i++)
		  if(TS.merge(c[i].u,c[i].v)) insert(c[i].u,c[i].v);
		dfs(1);
		for(int i=1;i<tot;i++) color(c[i].u,c[i].v,c[i].w);
		for(int i=1;i<=k;i++)
		  if(s&(1<<i-1))
		  {
		  	if(f[b[i].u]==b[i].v) us[b[i].u]=1;
		  	else us[b[i].v]=1;
		  }
		getmax(ans,dp(1));
	}
	printf("%lld\n",ans);
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值