zoukankan      html  css  js  c++  java
  • P3639[APIO2013]道路费用【最小生成树】

    正题

    题目链接:https://www.luogu.com.cn/problem/P3639


    题目大意

    给出\(n\)个点\(m\)条有边权的无向图,然后再给出\(k\)条边权未定义的边,然后每个点有一个人数\(p_i\)

    现在要你给未确定的边权的边确定边权然后选出图的一棵最小生成树,之后所有点上的人都从自己的点走到根节点,当一个人经过刚刚确定边权的边时会支付这条边的权值的费用,现在要求总费用和最大。

    保证\(m\)条边的图联通且权值互不相同。

    \(1\leq n\leq 10^5,1\leq m\leq 3\times 10^5,1\leq k\leq 20\)


    解题思路

    突破口肯定在于权值互不相同,因为这样的话最小生成树就唯一了,然后发现我们加上\(k\)条边后最多替换掉原来图上的\(k\)条边,所以大部分的边都是和原来的相同的。

    我们可以先把这\(k\)条边连接上,然后跑一棵最小生成树,再吧这\(k\)条边去掉这样就最多会产生\(k+1\)个连通块。

    然后再用联通块跑一次最小生成树,把这些边记下来,这些边是可能使用上的。

    之后我们\(2^k\)枚举哪些边选不选入最小生成树上,然后拿上面的边跑最小生成树,之后每条边的权值就是所有连接分割的两个联通块的最小边权,这个我们可以枚举边然后直接暴力跳,之后统计答案就好了。

    时间复杂度:\(O(m\log m+2^kk^2)\)


    code

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<vector>
    #define ll long long
    using namespace std;
    const ll N=3e5+10,K=22;
    struct edge{
    	ll x,y,w;
    }e[N];
    ll n,m,k,cnt,answer,p[N],fa[N],fb[N],rev[N];
    ll r[K],w[K],dep[K],f[K],dx[K],dy[K],mn[K];
    vector<int>G[K];ll a[K][K];
    bool cmp(edge x,edge y)
    {return x.w<y.w;}
    ll find(ll x)
    {return (fa[x]==x)?x:(fa[x]=find(fa[x]));}
    ll finb(ll x)
    {return (fb[x]==x)?x:(fb[x]=finb(fb[x]));}
    void dfs(ll x,ll fa){
    	r[x]=w[x];f[x]=fa;dep[x]=dep[fa]+1;
    	for(ll i=0;i<G[x].size();i++){
    		ll y=G[x][i];
    		if(y==fa)continue;
    		dfs(y,x);r[x]+=r[y];
    	}
    	return;
    }
    signed main()
    {
    	scanf("%lld%lld%lld",&n,&m,&k);
    	for(ll i=1;i<=m;i++){
    		ll x,y,w;
    		scanf("%lld%lld%lld",&x,&y,&w);
    		e[i]=(edge){x,y,w};
    	}
    	sort(e+1,e+1+m,cmp);
    	for(ll i=1;i<=n;i++)fa[i]=fb[i]=i;
    	for(ll i=0;i<k;i++){
    		scanf("%lld%lld",&dx[i],&dy[i]);
    		ll x=find(dx[i]),y=find(dy[i]);
    		if(x==y)continue;fa[x]=y;
    	}
    	for(ll i=1;i<=n;i++)scanf("%lld",&p[i]);
    	for(ll i=1;i<=m;i++){
    		ll x=e[i].x,y=e[i].y;
    		x=find(x);y=find(y);
    		if(x==y)continue;fa[x]=y;
    		fb[finb(e[i].x)]=finb(e[i].y);
    	}
    	for(ll i=1;i<=n;i++)
    		if(finb(i)==i)rev[i]=++cnt,fa[cnt]=cnt;
    	for(ll i=1;i<=n;i++)rev[i]=rev[finb(i)];
    	for(ll i=1;i<=n;i++)w[rev[i]]+=p[i];
    	memset(a,0x3f,sizeof(a));int pm=m;m=0;
    	for(ll i=1;i<=pm;i++){
    		ll x=e[i].x,y=e[i].y,w=e[i].w;
    		x=rev[x];y=rev[y];
    		if(find(x)==find(y))continue;
    		fa[find(x)]=find(y);
    		e[++m]=(edge){x,y,w};
    	}
    	sort(e+1,e+1+m,cmp);
    	ll MS=(1<<k);
    	for(ll i=0;i<k;i++)dx[i]=rev[dx[i]],dy[i]=rev[dy[i]];
    	for(ll s=0;s<MS;s++){
    		memset(mn,0x3f,sizeof(mn));
    		for(ll i=1;i<=cnt;i++)
    			fa[i]=i,G[i].clear();
    		bool flag=0;
    		for(ll i=0;i<k;i++){
    			if(!((s>>i)&1))continue;
    			ll x=find(dx[i]),y=find(dy[i]);
    			if(x==y){flag=1;break;}fa[x]=y;
    			G[dx[i]].push_back(dy[i]);
    			G[dy[i]].push_back(dx[i]);
    		}
    		if(flag)continue;
    		for(ll i=1;i<=m;i++){
    			ll x=find(e[i].x),y=find(e[i].y);
    			if(x==y)continue;fa[x]=y;
    			G[e[i].x].push_back(e[i].y);
    			G[e[i].y].push_back(e[i].x);
    		}
    		dfs(rev[1],0);
    		for(ll i=1;i<=m;i++){
    			ll x=e[i].x,y=e[i].y;
    			while(x!=y){
    				if(dep[x]<dep[y])swap(x,y);
    				mn[x]=min(mn[x],e[i].w);x=f[x];
    			}
    		}
    		ll ans=0;
    		for(ll i=0;i<k;i++){
    			if(!((s>>i)&1))continue;
    			ll x=dx[i],y=dy[i];
    			if(dep[x]<dep[y])swap(x,y);
    			ans+=mn[x]*r[x];
    		}
    		answer=max(answer,ans);
    	}
    	printf("%lld\n",answer);
    	return 0;
    }
    
  • 相关阅读:
    Codeforces 401C Team 贪心法
    C++ 编译,执行过程 具体解释。
    [从头学数学] 第156节 概率初步
    关于flex,好像有12个属性非常重要
    数据清洗小记(12):姓与名的提取
    理解Angular中的$apply()以及$digest()
    Oracle开发者守则
    黑马程序猿——25,打印流,合并流,对象序列化,管道流,RandomAccessFile
    GPU 编程入门到精通(四)之 GPU 程序优化
    Python 面向对象编程 继承 和多态
  • 原文地址:https://www.cnblogs.com/QuantAsk/p/15430226.html
Copyright © 2011-2022 走看看