WQS 二分
[国家集训队] Tree I
题目描述
给你一个无向带权连通图,每条边是黑色或白色。让你求一棵最小权的恰好有
\(need\) 条白色边的生成树。
题目保证有解。
输入格式
第一行 \(V,E,need\)
分别表示点数,边数和需要的白色边数。
接下来 \(E\) 行,每行 \(s,t,c,col\) 表示这边的端点(点从 \(0\) 开始标号),边权,颜色(\(0\) 白色 \(1\) 黑色)。
输出格式
一行,表示所求生成树的边权和。
样例 #1
样例输入 #1
样例输出 #1
提示
对于 \(5\%\) 的数据,\(V\leq 10\) 。
对于另 \(15\%\) 的数据,\(V\leq 15\) 。
对于 \(100\%\) 的数据,\(V\leq 5\times10^4,E\leq 10^5\) 。
所有数据边权为 \([1,100]\)
中的正整数。
By WJMZBMR
二分给白色边权加上一个权值,如果白色边加的比较多,就把这个权值变大,不然就把这个权值变小,从而实现了无限制进行求解。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 #include <bits/stdc++.h> using namespace std;const int maxn = 5e4 + 10 , maxm = 1e5 + 10 ;int fa[maxn];struct node { int u,v,w,col; bool operator <(const node &x)const { if (w!=x.w)return w<x.w; return col<x.col; } }a[maxm]; int n,m,k;int find (int x) { while (x!=fa[x])x=fa[x]=fa[fa[x]]; return x; } #define mp make_pair pair<int ,int > check (int x) { for (int i=1 ;i<=n;i++)fa[i]=i; for (int i=1 ;i<=m;i++)if (a[i].col==0 )a[i].w+=x; sort (a+1 ,a+1 +m); int cnt=0 ,sz=0 ,sum=0 ; for (int i=1 ;i<=m;i++) { int u=a[i].u,v=a[i].v; u=find (u),v=find (v); if (u!=v) { fa[u]=v;cnt++;sz+=(!a[i].col); sum+=a[i].w; } } for (int i=1 ;i<=m;i++) if (a[i].col==0 )a[i].w-=x; return mp (sz,sum-x*k); } int main () { cin>>n>>m>>k; for (int i=1 ;i<=m;i++) { int u,v,w,col; cin>>u>>v>>w>>col; u++;v++; a[i]=(node){u,v,w,col}; } int L=-100 ,R=100 ,ans=0 ; while (L<=R){ int mid=(L+R)>>1 ; int x=check (mid).first; if (x>=k)L=mid+1 ,ans=mid; else if (x<k)R=mid-1 ; } cout<<check (ans).second; return 0 ; }
最小度限制生成树
题目描述
给你一个有 \(n\) 个节点,\(m\)
条边的带权无向图,你需要求得一个生成树,使边权总和最小,且满足编号为
\(s\) 的节点正好连了 \(k\) 条边。
输入格式
第一行四个数:\(n,m,s,k\)
下面的 \(m\) 行,每行三个整数:\(u,v,w\) ,表示有一条 \(u\) 连向 \(v\) 权值为 \(w\) 的边。
输出格式
输出一个数:满足要求的生成树的总边权。
可能会出现无解的情况,如果无解,则输出 Impossible
。
样例 #1
样例输入 #1
1 2 3 4 5 6 7 8 5 7 1 1 1 3 1 2 1 5 4 2 3 2 5 4 5 1 2 3 5 7 4 1 6
样例输出 #1
提示
数据范围
对于 \(20\%\) 的数据,\(n \le 10\) ,\(m
\le 30\) 。
对于 \(50\%\) 的数据,\(n \le 1000\) ,\(m
\le 5000\) 。
对于 \(100\%\) 的数据,\(1\leq n \le 5\times 10^4\) ,$1m ^5 $,\(1\leq k \le 100\) ,\(0\leq w\leq 3\times 10^4\) 。
注意
本题设有 hack 数据(Subtask \(2\) ),计 \(0\) 分,但若没有通过 hack
数据则不算通过本题。
本题也是如此,只是对象换成了与s相连的边而言。细节比上一题多了不少,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 #include <bits/stdc++.h> using namespace std;const int maxn = 5e4 + 10 , maxm = 5e5 + 10 ;const int INF = 0x3f3f3f3f ;struct node { int u,v,w; bool operator <(const node &x)const { return w<x.w; } }q1[maxm],q2[maxm]; int n,m,s,k;int cnt1,cnt2,fa[maxn];inline int find (int x) { while (x!=fa[x])x=fa[x]=fa[fa[x]]; return x; } #define mp make_pair pair<int ,int > check (int x) { int L1=1 ,L2=1 ; int cnt=0 ,sum=0 ; for (int i=1 ;i<=n;i++)fa[i]=i; while (L1<=cnt1 && L2<=cnt2) { if (q1[L1].w+x<=q2[L2].w) { node t=q1[L1]; int u=t.u,v=t.v,w=t.w+x; u=find (u);v=find (v); if (u!=v)fa[u]=v,cnt++,sum+=w; L1++; } else { node t=q2[L2]; int u=t.u,v=t.v,w=t.w; u=find (u);v=find (v); if (u!=v)fa[u]=v,sum+=w; L2++; } } while (L1<=cnt1) { node t=q1[L1]; int u=t.u,v=t.v,w=t.w+x; u=find (u);v=find (v); if (u!=v)fa[u]=v,cnt++,sum+=w; L1++; } while (L2<=cnt2) { node t=q2[L2]; int u=t.u,v=t.v,w=t.w; u=find (u);v=find (v); if (u!=v)fa[u]=v,sum+=w; L2++; } return mp (cnt,sum-k*x); } int main () { cin>>n>>m>>s>>k; for (int i=1 ;i<=m;i++){ int u,v,w; cin>>u>>v>>w; node t=(node){u,v,w}; if (u==s || v==s) q1[++cnt1]=t; else q2[++cnt2]=t; } sort (q1+1 ,q1+1 +cnt1); sort (q2+1 ,q2+1 +cnt2); int L=-INF,R=INF; if (check (L).first<k || check (R).first>k){puts ("Impossible" );return 0 ;} int ans=0 ; while (L<=R){ int mid=(L+R)>>1 ; if (check (mid).first>=k)ans=mid,L=mid+1 ; else R=mid-1 ; } cout<<check (ans).second; return 0 ; }
April Fools' Problem (hard)
题面翻译
\(n\) 道题, 第 \(i\) 天可以花费 \(a_i\) 准备一道题, 花费 \(b_i\) 打印一道题, 每天最多准备一道,
最多打印一道, 准备的题可以留到以后打印, 求最少花费使得准备并打印 \(k\) 道题。
题目描述
The plans for HC $ ^{2} $ are rather far-fetched: we are just over
500 000 days away from HC $ ^{2} $ 3387, for example, and accordingly we
are planning to have a couple hundred thousand problems in that edition
(we hope that programming contests will become wildly more popular). The
marmots need to get to work, and they could use a good plan...
输入格式
Same as the medium version, but the limits have changed: $
1<=k<=n<=500000 $ .
输出格式
Same as the medium version.
样例 #1
样例输入 #1
1 2 3 8 4 3 8 7 9 9 4 6 8 2 5 9 4 3 8 9 1
样例输出 #1
其实不是一上来就学这个,而是要学反悔贪心的。
这题其实很好反悔,如果遇到比较小的时间来打印就打印,然后放一个-b[i]进去,用来抵消.
加的那个权值就是给打印加的了(因为k是限制的是打印)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 #include <bits/stdc++.h> using namespace std;#define LL long long const int maxn = 5e5 + 10 ;LL a[maxn],b[maxn]; int n,k;#define read() read<LL> () #define Pair pair<LL,int> #define mp make_pair priority_queue<Pair,vector<Pair>,greater<Pair> > q; pair<int ,LL> check (LL x) { while (q.size ())q.pop (); int cnt=0 ;LL sum=0 ; for (int i=1 ;i<=n;i++) { q.push (mp (a[i],0 )); LL tmp=b[i]-x+q.top ().first; if (tmp<0 ) { sum+=tmp; q.pop (); q.push (mp (-b[i]+x,1 )); } } while (q.size ()) cnt+=q.top ().second,q.pop (); return mp (cnt,sum+1LL *k*x); } int main () { cin>>n>>k; for (int i=1 ;i<=n;i++) cin>>a[i]; for (int i=1 ;i<=n;i++) cin>>b[i]; LL L=0 ,R=2e9 ; int ans=0 ; while (L<=R) { LL mid=(L+R)>>1 ; if (check (mid).first>=k)ans=mid,R=mid-1 ; else L=mid+1 ; } cout<<check (ans).second; return 0 ; }
参考资料:
[总结]
wqs 二分 - ¶凉笙 - 博客园 (cnblogs.com)
洛谷日报-wqs二分
学习笔记