Luogu P2619 [国家集训队2]Tree I
作者:互联网
给你一个无向带权连通图,每条边是黑色或白色。让你求一棵最小权的恰好有need条白色边的生成树。
这个算法叫..WQS二分/带权二分/DP凸优化
用来解决一种特定类型的问题:
有n个物品,选择每一个都会有相应的费用,需要求出强制选need个物品时的最大/最小费用。
适用范围:设$f(x)$为选$x$个物品的费用,$f(x)$为凸函数。
并不会证明 感性理解一下:
对于这道题,跑一遍最小生成树,如果选择的白边不够,就要增加白边的优先级。
在kruskal算法中,选择每条边的优先级即为这条边的权值(越小越好)。
那么,把每条白边的权值减少一个值x,再跑一边最小生成树。
如果选择的白边多了,就要减少白边的优先级。
用二分答案来枚举这个增加的值x。
不知道为什么新开一个变量记录x会WA...
完整代码如下
#include<cstdio> #include<iostream> #include<cmath> #include<cstring> #define MogeKo qwq #include<algorithm> using namespace std; const int maxn = 1e5+10; int n,m,k,x,y; int cnt,sum; int fa[maxn]; struct edge { int u,v,val,col; bool operator < (const edge & N)const { return val<N.val || (val==N.val && col<N.col); } } e[maxn]; int getfa(int x) { if(fa[x] == x)return x; return fa[x] = getfa(fa[x]); } void init() { for(int i = 1; i <= n; i++) fa[i] = i; } void kruskal() { cnt = sum = 0; init(); sort(e+1,e+m+1); int ecnt = 0; for(int i = 1; i <= m; i++) { int uu = getfa(e[i].u); int vv = getfa(e[i].v); if(uu == vv) continue; fa[uu] = vv; sum += e[i].val; cnt += e[i].col^1; if(++ecnt == n-1)break; } } int BS() { int ans = 0; int l = -105,r = 105,mid; while(l <= r) { mid = (l+r)>>1; for(int i = 1; i <= m; i++) if(e[i].col == 0) e[i].val += mid; kruskal(); if(cnt >= k) { ans = sum-(mid*k); l = mid+1; } else r = mid-1; for(int i = 1; i <= m; i++) if(e[i].col == 0) e[i].val -= mid; } return ans; } int main() { scanf("%d%d%d",&n,&m,&k); for(int i = 1; i <= m; i++) { scanf("%d%d%d%d",&x,&y,&e[i].val,&e[i].col); e[i].u = ++x; e[i].v = ++y; } printf("%d",BS()); return 0; }View Code
标签:const,int,Luogu,白边,mid,Tree,优先级,include,国家集训队 来源: https://www.cnblogs.com/mogeko/p/10993014.html