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;
    }
    
  • 相关阅读:
    5.4 省选模拟赛 修改 线段树优化dp 线段树上二分
    一本通 高手训练 1782 分层图 状压dp
    luogu P3830 [SHOI2012]随机树 期望 dp
    5.2 省选模拟赛 或许 线型基
    luogu P4562 [JXOI2018]游戏 组合数学
    一本通 高手训练 1781 死亡之树 状态压缩dp
    luogu P4726 【模板】多项式指数函数 多项式 exp 牛顿迭代 泰勒展开
    4.28 省选模拟赛 负环 倍增 矩阵乘法 dp
    HDU 1756 Cupid's Arrow 计算几何 判断一个点是否在多边形内
    一本通 高手训练 1763 简单树 可持久化线段树 树链刨分 标记永久化
  • 原文地址:https://www.cnblogs.com/ZX-GO/p/15080374.html
Copyright © 2011-2022 走看看