zoukankan      html  css  js  c++  java
  • [BZOJ2654]tree

    题目描述 Description###

    给你一个无向带权连通图,每条边是黑色或白色。让你求一棵最小权的恰好有(need) 条白色边的生成树。
    题目保证有解。

    输入描述 Input Description###

    第一行(V) ,(E) ,(need) 分别表示点数,边数和需要的白色边数。
    接下来E行,每行(s) ,(t) ,(c) ,(col) 表示这边的端点(点从0开始标号),边权,颜色(0白色1黑色)。

    输出描述 Output Description###

    一行表示所求生成树的边权和。

    样例输入 Sample Input###

    2 2 1
    0 1 1 1
    0 1 2 0
    

    样例输出 Sample Output###

    2
    

    数据范围及提示 Data Size & Hint###

    (V<=50000) ,(E<=100000) ,所有数据边权为([1,100]) 中的正整数。

    之前的一些废话###

    题解###

    题目条件设定过于苛刻,想一想如何影响到最小生成树中白边的数量,是不是白边权值越小,数量越多,白边权值越大,数量越少?好的,想到这一步就很棒棒了,然后我们就可以二分了,二分对所有白边加上一个数(正数负数均可),然后对整个图进行(Kruskal) ,顺便记录此时最小生成树加入了多少条白边,注意答案最后要减掉(need*index) ((index) 为二分的值)。说到这里,主算法就差不多了,复杂度(O(n log^2 n)) 但是还是有一个很严重的细节没有考虑到,就是我们在对边排序的时候,必须优先排白边,这样我们求出的至多要选多少条白边,所以最后选取答案的时候条件并不是(cntw=need) ,而是(cntw>=need) ,其中(cntw) 为选取白边的数量。

    代码###

    #include<iostream>
    #include<algorithm>
    #include<cmath>
    #include<cstdio>
    #include<queue>
    #include<cstring>
    using namespace std;
    typedef long long LL;
    typedef pair<int,int> PII;
    #define mem(a,b) memset(a,b,sizeof(a))
    inline int read()
    {
    	int x=0,f=1;char c=getchar();
    	while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
    	while(isdigit(c)){x=x*10+c-'0';c=getchar();}
    	return x*f;
    }
    const int maxn=100010,oo=2147483647;
    int n,m,need,f[maxn],u[maxn],v[maxn],w[maxn],c[maxn],L,R,ans=oo;
    struct Edge
    {
    	int u,v,w,c;
    	Edge() {}
    	Edge(int _1,int _2,int _3,int _4):u(_1),v(_2),w(_3),c(_4) {} 
    	bool operator < (const Edge&s)const {return w==s.w ? c<s.c : w<s.w;}
    }e[maxn];
    int getf(int x){return x==f[x] ? x : f[x]=getf(f[x]);}
    void merge(int a,int b)
    {
    	int x=getf(a),y=getf(b);
    	if(x!=y)f[x]=y; 
    }
    bool judge(int index)
    {
    	for(int i=1;i<=n;i++)f[i]=i;
    	for(int i=0;i<m;i++)
    	{
    		e[i].u=u[i];e[i].v=v[i];
    		e[i].w=w[i];e[i].c=c[i];
    		if(!c[i])e[i].w+=index;
    	}
    	sort(e,e+m);
    	int cnt=0,cntw=0,sum=0;
    	for(int i=0;i<m;i++)
    		if(getf(e[i].u)!=getf(e[i].v))
    		{
    			merge(e[i].u,e[i].v);
    			if(!e[i].c)cntw++;
    			sum+=e[i].w;
    			cnt++;
    			if(cnt==n-1)break;
    		}
    	if(cntw>=need)ans=min(ans,sum-cntw*index);
    	return cntw<=need;
    }
    int main()
    {
        n=read();m=read();need=read();
        for(int i=0;i<m;i++)u[i]=read()+1,v[i]=read()+1,w[i]=read(),c[i]=read();
        L=-100;R=100;
    	while(R-L>1)
    	{
    		int mid=(L+R)>>1;
    		if(judge(mid))R=mid;
    		else L=mid;
    	}
    	judge(L);judge(R);
    	printf("%d
    ",ans);
    	return 0;
    }
    

    总结###

    此题过于坑爹,我觉得能想到二分已经挺强的了,但是至于能否想到上述提到的那个细节。。。无语凝噎。

  • 相关阅读:
    ERP类系统设计学习
    人工智能关键词
    系统性能
    连接不同服务器不同数据库
    socket一个例子
    SQLite
    asp.net 页面缓存、数据缓存
    原生js
    Android 网络调试 adb tcpip 开启方法
    C语言中string char int类型转换
  • 原文地址:https://www.cnblogs.com/FYH-SSGSS/p/7719171.html
Copyright © 2011-2022 走看看