zoukankan      html  css  js  c++  java
  • 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;
    }
    
  • 相关阅读:
    HDU 1501 Zipper(DFS)
    HDU 2181 哈密顿绕行世界问题(DFS)
    HDU 1254 推箱子(BFS)
    HDU 1045 Fire Net (DFS)
    HDU 2212 DFS
    HDU 1241Oil Deposits (DFS)
    HDU 1312 Red and Black (DFS)
    HDU 1010 Tempter of the Bone(DFS+奇偶剪枝)
    HDU 1022 Train Problem I(栈)
    HDU 1008 u Calculate e
  • 原文地址:https://www.cnblogs.com/ZX-GO/p/15080374.html
Copyright © 2011-2022 走看看