• [BZOJ2654]:tree(Kruskal+WQS二分)


    题目传送门


    题目描述

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


    输入格式

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


    输出格式

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


    样例

    样例输入:

    2 2 1
    0 1 1 1
    0 1 2 0

    样例输出:

    2


    数据范围与提示

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


    题解

    看到题很恐怖,首先,应该都能想到最小生成树,因为题目上就说了嘛~

    但是还有要求,要恰好有need条白边,那么问题就复杂了。

    考虑一下,如果我们对所有白边的权值都加或减一个值,然后再跑Kruskal,使用白边的个数就会发生变化,我们就找这样一个值,使使用白边个数为need,然后用这个权值进行计算,将当前情况下所有的边都加进答案,然后最后再减去need×权值,得出的结果极为答案。

    发现边权为[1,100]中的正整数,所以时间复杂度O(200×E)

    然而:

    发现BZOJ总是能给你意外的惊喜……

    优化时间复杂度,考虑二分法:

      1.二分法QJ测试点:

      然后你能在BZOJ上而分出这个结果:完全无视白边个数这个问题,直接求最小生成树……

      

      内心过于震惊!!!

      标程:

       

      说实话,这样有些不道德,毕竟……

      

      无论如何,这道题用来检验你的最小生成树有没有打对还是好的^_^

      2.显然上面那种做法很不道德,严重QJ测试点行为!!!那么考虑二分答案进行优化,二分所有白边加或减的这个权值,如果白边个数不足need,则权值要减,反之同理。

    时间复杂度O(7×E)

    那么你可能会有疑问,如果出现这样一种情况,当权值为w时,使用白边的个数<need,但是当权值为w-1时,使用白边的个数又>need了,然而题目要求我们求一棵最小权的 恰好need条白色边的生成树,那么这种做法的正确性又怎么论证呢?

    这样思考,其实当w-1时,增加的白边个数其实也就是权值为w时把它们挤掉的那些黑边,所以其实多出来的白边都可以用黑边代替,所以就不用担心这些问题了。


    代码时刻

    暴力:

    #include<bits/stdc++.h>
    using namespace std;
    struct rec
    {
    	int s;
    	int t;
    	int c;
    	bool col;
    }e[100001],new_e[100001];//e表示原边,new_e用来存储暂时加权值的边
    int V,E,need;
    int f[50001];
    int val;
    int sum;
    void change(int x,int val)//给new_e赋值
    {
    	new_e[x].s=e[x].s;
    	new_e[x].t=e[x].t;
    	new_e[x].c=e[x].c+(e[x].col^1)*val;//如果白边就加上val,如果是黑边则不加
    	new_e[x].col=e[x].col;
    }
    bool cmp(rec a,rec b){if(a.c==b.c)return a.col<b.col;return a.c<b.c;}//结构体排序
    int find(int x){return f[x]==x?x:f[x]=find(f[x]);}//并查集
    bool judge(int x)//判断可不可以
    {
    	int ans=0,cnt=0;
    	for(int i=1;i<=V;i++)f[i]=i;//并查集记得初始化
    	for(int i=1;i<=E;i++)change(i,x);
    	sort(new_e+1,new_e+E+1,cmp);
    	for(int i=1;i<=E;i++)
    	{
    		int xx=find(new_e[i].s);
    		int yy=find(new_e[i].t);
    		if(xx==yy)continue;
    		cnt++;
    		ans+=new_e[i].col^1;
    		sum+=new_e[i].c;
    		f[xx]=yy;
    		if(cnt==V-1)
    		if(ans>=need)return 1;
    		else return 0;
    	}
    }	
    int main()
    {
    	scanf("%d%d%d",&V,&E,&need);
    	for(int i=1;i<=E;i++)
    	{
    		scanf("%d%d%d%d",&e[i].s,&e[i].t,&e[i].c,&e[i].col);
    		e[i].s++;
    		e[i].t++;
    	}
    	int ans;
    	for(int i=100;i>=-100;i--)//爆力枚举答案
    	{
    		sum=0;
    		if(judge(i)){ans=sum-need*i;break;}
    	}
    	cout<<ans<<endl;
    	return 0;
    }
    

    QJ测试点,说白了就是Kruskal板子:

    #include<bits/stdc++.h>
    using namespace std;
    struct rec
    {
        int s;
        int t;
        int c;
    }e[100001];
    bool col;
    int V,E,need;
    int f[50001];
    int val;
    int ans,cnt;
    bool cmp(rec a,rec b){return a.c<b.c;}
    int find(int x){return f[x]==x?x:f[x]=find(f[x]);}
    int main()
    {
        scanf("%d%d%d",&V,&E,&need);
        for(int i=1;i<=E;i++)
        {
            scanf("%d%d%d%d",&e[i].s,&e[i].t,&e[i].c,&col);
            e[i].s++;
            e[i].t++;
        }
        for(int i=1;i<=V;i++)f[i]=i;
        sort(e+1,e+E+1,cmp);
        for(int i=1;i<=E;i++)
        {
            int x=find(e[i].s);
            int y=find(e[i].t);
            if(x==y)continue;
            cnt++;
            ans+=e[i].c;
            f[x]=y;
            if(cnt==V-1)break;
        }
        cout<<ans;
        return 0;
    }
    

    正解:

    #include<bits/stdc++.h>
    using namespace std;
    struct rec
    {
    	int s;
    	int t;
    	int c;
    	bool col;
    }e[100001],new_e[100001];
    int V,E,need;
    int f[50001];
    int val;
    int sum;
    void change(int x,int val)
    {
    	new_e[x].s=e[x].s;
    	new_e[x].t=e[x].t;
    	new_e[x].c=e[x].c+(e[x].col^1)*val;
    	new_e[x].col=e[x].col;
    }
    bool cmp(rec a,rec b){if(a.c==b.c)return a.col<b.col;return a.c<b.c;}
    int find(int x){return f[x]==x?x:f[x]=find(f[x]);}
    bool judge(int x)
    {
    	int ans=0,cnt=0;
    	for(int i=1;i<=V;i++)f[i]=i;
    	for(int i=1;i<=E;i++)change(i,x);
    	sort(new_e+1,new_e+E+1,cmp);
    	for(int i=1;i<=E;i++)
    	{
    		int xx=find(new_e[i].s);
    		int yy=find(new_e[i].t);
    		if(xx==yy)continue;
    		cnt++;
    		ans+=new_e[i].col^1;
    		sum+=new_e[i].c;
    		f[xx]=yy;
    		if(cnt==V-1)
    		if(ans>=need)return 1;
    		else return 0;
    	}
    }	
    int main()
    {
    	scanf("%d%d%d",&V,&E,&need);
    	for(int i=1;i<=E;i++)
    	{
    		scanf("%d%d%d%d",&e[i].s,&e[i].t,&e[i].c,&e[i].col);
    		e[i].s++;
    		e[i].t++;
    	}
    	int lft=-105,rht=105,ans;
    	while(lft<=rht)//二分答案
    	{
    		sum=0;
    		int mid=(lft+rht)>>1;
    		if(judge(mid))
    		{
    			ans=sum-need*mid;
    			lft=mid+1;
    		}
    		else rht=mid-1;
    	}
    	cout<<ans<<endl;
    	return 0;
    }
    

    rp++

  • 相关阅读:
    科学计算和可视化
    利用Python制作GIF图片
    模拟体育竞技分析
    词云(傲慢与偏见)
    词频统计+词云(傲慢与偏见)
    汉诺塔问题
    Python 的turtle笔记
    有进度条的圆周率计算
    Python 第二周练习
    warning: deprecated conversion from string constant to ‘char*’
  • 原文地址:https://www.cnblogs.com/wzc521/p/11175369.html
Copyright © 2020-2023  润新知