洛谷P2619 [国家集训队2]Tree I(带权二分,Kruscal,归并排序)

洛谷题目传送门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;
}
相关文章
相关标签/搜索