洛谷题目传送门html
给一个比较有逼格的名词——WQS二分/带权二分/DP凸优化(固然这题不是DP)。函数
用来解决一种特定类型的问题:优化
有\(n\)个物品,选择每个都会有相应的权值,须要求出强制选\(need\)个物品时的最大/最小权值和。spa
通常来讲,咱们求不限制个数的最大/最小权值和很容易,但在限制个数的前提下再求最值会变得有点困难。比较低效的作法是对状态再加设一个维度表示已选物品数量,而后经过DP等方法求出。code
应用前提:设\(g_x\)为强制选\(x\)个物品的最大/最小权值和,若是全部的点对\((x,g_x)\)在平面上可以构成一个凸包,那么能够考虑使用WQS二分。htm
因此说用三个名词合指它也不为过(提出者WQS,二分的量是权值增量,使用前提是凸函数)blog
WQS的论文这里能够下载排序
建议食用Creeper_LKF大佬的blog,数形结合的分析过程已经很是完整了。ip
简单的来讲,咱们不能知道这个凸包长什么样子,但咱们能够拿着一个斜率为\(k\)的直线去切这个凸包,至关于给每一个物品附加了一个权值\(k\)。设直线的截距为\(b\),那么选\(x\)个物品后总权值就会等于\(b+kx\)。咱们经过\(O(n)\)的DP等方法找到最大的\(b\),同时也能够求出选了的个数\(x\),经过\(x\)与\(need\)的关系来调整直线斜率继续二分。get
拿本题来讲,选\(x\)条白边,能够写个平方DP而后发现\(g_x\)是个下凸函数。而后咱们在\([-100,100]\)(显然是斜率的上下界,由于更改一条边带来的权值和的更改不会超过\(100\))的范围内二分\(k\),以后全部白边的权值增长\(k\),跑一遍Kruscal统计选了多少条白边。若是这个数量大于等于\(need\)就调大\(k\),不然调小。
最后斜率为\(mid\)的直线与凸包的切点就是答案,注意从中减去\(k\)的影响(ans-=mid*x
)
边界问题Creeper_LKF大佬也证实了只要在边权相等的时候优先选白边就没问题了。
写法上有一个优化:每次Kruscal的时候不用从新排序了。由于每次咱们只是给全部白边总体加一个权值,因此若是咱们先把白边和黑边分开排序的话,加完之后也仍是有序的。每次Kruscal时只要用相似归并排序的方法\(O(E)\)的扫一遍就能够啦!复杂度从\(O(E\log E\log 200)\)降到了\(O(E\log E+E\log200)\),目前暂时rank1,欢迎超越。
#include<cstdio> #include<algorithm> #define RG register #define R RG int #define G if(++ip==iend&&fread(ip=buf,1,N,stdin)) using namespace std; const int N=5e5+9,M=1e6+9; char buf[N],*iend=buf+N,*ip=iend-1; inline int in(){ while(*ip<'-')G; R x=*ip&15;G; while(*ip>'-'){x=x*10+(*ip&15);G;} return x; } struct Edge{ int u,v,w;bool c; inline bool operator<(RG Edge&x){ return w<x.w; } }e[M]; int f[N]; int getf(R x){ return x==f[x]?x:f[x]=getf(f[x]); } inline bool add(R i){//尝试加边并返回是否成功 if(getf(e[i].u)==getf(e[i].v))return 0; f[f[e[i].u]]=f[e[i].v];return 1; } int main(){ R n=in(),mw=0,m=in(),need=in(),i,j;//mw为白边数量 for(i=0;i<m;++i){//点和边都从0开始存了 e[i].u=in();e[i].v=in();e[i].w=in(); if(!(e[i].c=in()))swap(e[mw++],e[i]);//将黑白边分开 } sort(e,e+mw);sort(e+mw,e+m); R l=-100,r=100,mid,ans; while(l<r){ mid=(l+r+1)>>1; for(i=0;i<n;++i)f[i]=i; ans=i=0;j=mw; while(i<mw&&j<m)//类归并排序 e[i].w+mid<=e[j].w?ans+=add(i++):add(j++); while(i<mw)ans+=add(i++);//白边数量统计完整 ans<need?r=mid-1:l=mid; } mid=l; for(i=0;i<n;++i)f[i]=i; ans=i=0;j=mw;//最后算权值总和 while(i<mw&&j<m) if(e[i].w+mid<=e[j].w) ans+=(e[i].w+mid)*add(i),++i; else ans+=e[j].w*add(j),++j; while(i<mw)ans+=(e[i].w+mid)*add(i),++i; while(j<m )ans+=e[j].w*add(j),++j; printf("%d\n",ans-need*mid);//减掉 return 0; }