• HDU多校第三场1010/HDU-6982 Road Discount【包含恰好k条黑边的最小生成树】


    题目链接

    思路

    官方题解写的很清楚,将原边看成白边,折扣边看成黑边,求恰好黑边数为(k)的最小生成树。

    这边有道一模一样的子问题BZOJ2654-tree.

    我们需要设定一个(x)值,对每条黑边的长度算上(+x)的贡献,然后进行一次最小生成树,假如最小生成树的最终值为(sum),选择了(cnt)条黑边,那么最小生成树的值就是(sum-cnt*x)。因为题目所给的边的范围为([0,1000]),所以(x)的范围为([-1000,1000]).这样可以保证每一种黑边的情况。

    在计算之前,首先可以把图缩小到(o(n))的级别,假如没有黑边,就是一个所有白边的最小生成树,没有白边,就是一个所有黑边的最小生成树。这样只有(O(2n))条边,那么对于最终黑白边混合的最小生成树,其所选的边一定是从这(2n)条边中选择。

    同时我们可以发现,当(x)值越小的时候,选择的黑边就会越多,满足单调性。

    对于寻找边数恰好为(k)的最小生成树,就有以下做法:

    1、二分一个(x),将所有的黑边的权值(+x)

    2、然后跑一遍最小生成树,记录最少需要黑边的数量(l_x)​,最多需要黑边的数量(r_x)

    3、若对于一个(k)满足(l_xleq k leq r_x),那么此时得到的(sum-cnt*x)​可以作为答案。

    在二分过程中每次查看返回的黑边数量是否满足要求即可。

    对于BZOJ2654-tree这题因为只是输出一个答案,所以直接二分求值即可。对于此题需要先预处理出(x)(-1000)(1000)的所有情况,然后再去(klogn)的复杂度查询即可,发现标程这边偷懒写了(kn)的写法。

    代码

    #include<bits/stdc++.h>
    using namespace std;
    
    typedef pair<int, int> pii;
    const int N = 1e3 + 10, M = 2e5 + 10;
    
    struct Edge {
    	int u, v, w;
    	friend bool operator < (const Edge &a, const Edge &b) {
    		return a.w < b.w;
    	}
    }e1[M], e2[M];
    int fa[N];
    int n, m;
    pii f[N * 2];
    
    int find(int x) {
    	return x == fa[x] ? x : fa[x] = find(fa[x]);
    }
    
    void init_edge() {
    	for(int i = 1; i <= n; i++) fa[i] = i;
    	sort(e1 + 1, e1 + 1 + m);
    	for(int i = 1, idx = 0; i <= m; i++) {
    		int fx = find(e1[i].u), fy = find(e1[i].v);
    		if(fx != fy) {
    			fa[fx] = fy;
    			e1[++idx] = e1[i];
    		}
    	}
    	
    	for(int i = 1; i <= n; i++) fa[i] = i;
    	sort(e2 + 1, e2 + 1 + m);
    	for(int i = 1, idx = 0; i <= m; i++) {
    		int fx = find(e2[i].u), fy = find(e2[i].v);
    		if(fx != fy) {
    			fa[fx] = fy;
    			e2[++idx] = e2[i];
    		}
    	}
    }
    
    bool merge(int x, int y) {
    	int fx = find(x), fy = find(y);
    	if(fx == fy) return 0;
    	fa[fx] = fy;
    	return 1;
    }
    
    pii calc(int x) {
    	int l = 1, r = 1, cnt = 0, sum = 0;
    	for(int i = 1; i <= n; i++) fa[i] = i;
    	while(l < n && r < n) {
    		if(e1[l].w > e2[r].w + x) {
    			if(merge(e2[r].u, e2[r].v)) {
    				sum += e2[r].w + x;
    				cnt++;
    			}
    			r++;
    		}
    		else {
    			if(merge(e1[l].u, e1[l].v)) {
    				sum += e1[l].w;
    			}
    			l++;
    		}
    	}
    	
    	while(l < n) {
    		if(merge(e1[l].u, e1[l].v)) {
    			sum += e1[l].v;
    		}
    		l++;
    	}
    	while(r < n) {
    		if(merge(e2[r].u, e2[r].v)) {
    			sum += e2[r].w + x;
    			cnt++;
    		}
    		r++;
    	}
    	return {sum, cnt};
    }
    
    int query(int x) {
    	int l = -1000, r = 1000;
    	int res = -1;
    	while(l <= r) {
    		int mid = l + r >> 1;
    		if(f[mid + 1000].second <= x) {
    			res = f[mid + 1000].first - x * mid;
    			r = mid - 1;
    		}
    		else l = mid + 1;
    	}
    	return res;
    }
    
    void solve() {
    	scanf("%d%d", &n, &m);
    	for(int i = 1; i <= m; i++) {
    		int u, v, w1, w2;
    		scanf("%d%d%d%d", &u, &v, &w1, &w2);
    		e1[i] = {u, v, w1};
    		e2[i] = {u, v, w2};
    	}
    	init_edge();
    	for(int k = -1000; k <= 1000; k++) f[k + 1000] = calc(k);
    	
    	for(int i = 0; i <= n - 1; i++) {
    		printf("%d
    ", query(i));
    	}
    }
    
    int main() {
    	int t; cin >> t; while(t--)
    	solve();
    	return 0;
    }
    
  • 相关阅读:
    高仿爱鲜蜂购物应用源码
    控制ClistCtrl的滚动的位置
    VC保存当面某个区域的图片
    MFC 屏幕截图方法
    回调函数使用(三)
    回调函数使用方法二
    VS2010编译Boost 1.57 静态链接库
    Windows7+VS2010下OpenGL的环境配置
    CxImage图像库的使用 .
    VS2010+PCL配置
  • 原文地址:https://www.cnblogs.com/ZX-GO/p/15080374.html
Copyright © 2020-2023  润新知