Road Discount HDU6982

HDU6982

题目大意:

给定\(n\)个点与\(m\)条白色与黑色边,计算出有且仅有k条黑色边的图的最小生成树,\(k=0,1,2,3......n-1\),数据保证有解。

这题我们要考虑两个问题:第一是如何再有且仅有\(k\)条黑色边的前提下求出最小生成树,第二是如何优化这个求带条件的最小生成树的效率。

我们考虑\(Krsual\)算法的流程:贪心的选取边权最小的边加入图中并用并查集维护。选出k条黑色边,那么我们就要以某种方式让黑色边的优先级有所上升或下降。具体的,我们可以给每条黑色边增加一权值\(x\)(\(x\)可能是负数),这样就能将黑色边的优先级提升/下降,更早/更晚的选到黑色边。最后再给总权值减去\(x*k\)即可。

常规情况下,我们要用二分来处理这个流程。但也有个值得考虑的边界问题:假如\(x=x_0\)时\(Krsual\)求出的最小生成树有\(k+1\)条黑边,而\(x=x_0+1\)情况时求出有\(k-1\)条黑边,那该怎么处理?我们注意到“题目保证有解”,那么说明当有\(k+1\)条黑边时是因为同边权情况下优先选择黑边导致一条白边被黑边替代而导致的,这时候我们也完全可以将其视为正确答案。

写到这里答案似乎已呼之欲出:枚举\(k\),并对每个\(k\)进行二分答案,复杂度大约为\(O(Knm\log{m})\)。虽然看似很优但对于本题是严重超时的。首先我们注意到,对于白色边,如果\(0\)条黑色边前提下跑\(Krsual\),那么若本次没被用到的边在\(k\)更大的情况下也不可能被用到,因为选择\(k\)条黑边本质上是用黑边替换白边,而竞争不过白色边的白色边连被黑色边替换的机会都没有,对于黑色边也是,由于对黑色边是统一操作,显然无论怎样操作黑色边的相对位置关系是不变的,那么我们就可以将边从\(m\)条减少至\(n-1\)条,大大提升了效率。此外,对于每次二分我们发现,我们本质是在对比二分出\(mid\)值后求最小生成树黑色边数与\(k\)的关系,我们可以选择先暴力枚举并记录二分结果,在枚举\(k\)后逐一比对即可。避免大量的重复计算二分出\(mid\)对应的值。

完整代码如下

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<queue>
#include<stack>
#include<string.h>
#include<cmath>
#include<deque>
#include<vector>
#define Lint  long long
using namespace std;
const int N=1005;
const int M=2e5+10;
struct node{
    int x,y,z,col;
};
pair<int,int>c[2005];
node a[M],b[M];
int T,n,m,f[N],cut,vis[M];
int sum,num,tot,ans;
int Find(int x){
    if(x==f[x]) return x;
    return f[x]=Find(f[x]);
}
bool cmp(node x,node y){
    return x.z<y.z;
}
int check(int x){
    for(int i=1;i<n;++i){
        b[i].z+=x;
    }
    int l=1,r=1;
    sum=0,tot=0,num=0;
    for(int i=1;i<=n;++i) f[i]=i;
    while(l<n&&r<n){
        if(a[l].z<b[r].z){
            int le=Find(a[l].x),re=Find(a[l].y);
            if(le!=re){
                f[le]=re;
                sum+=a[l].z;
            }
            l++;
        }
        else{
            int le=Find(b[r].x),re=Find(b[r].y);
            if(le!=re){
                f[le]=re;
                sum+=b[r].z;
                num++;
            }
            r++;
        }
    }
    while(l<n){
        int le=Find(a[l].x),re=Find(a[l].y);
            if(le!=re){
                f[le]=re;
                sum+=a[l].z;
            }
            l++;
    }
    while(r<n){
        int le=Find(b[r].x),re=Find(b[r].y);
            if(le!=re){
                f[le]=re;
                sum+=b[r].z;
                num++;
            }
            r++;
    }
    for(int i=1;i<n;++i){
        b[i].z-=x;
    }
    return num;
}
int ask(int x){
    for(int i=1000;i>=-1000;--i){
        if(c[i+1000].second>=x) return c[i+1000].first-i*x;;
    }
}
void krsual(node *x){
    int qwq=0;
    int qaq=0;
    for(int i=1;i<=n;++i) f[i]=i;
    for(int i=1;i<=m;++i){
        int le=Find(x[i].x),re=Find(x[i].y);
        if(le!=re){
            f[le]=re;
            x[++qaq]=x[i];
            qwq++;
        }
    }
}
int main(){
    int x,y,z,zz;
    scanf("%d",&T);
    while(T--){
        cut=0;
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;++i) f[i]=i;
        for(int i=1;i<=m;++i){
            scanf("%d%d%d%d",&x,&y,&z,&zz);
            a[i].x=x,a[i].y=y,a[i].z=z;
            b[i].x=x,b[i].y=y,b[i].z=zz;
            a[i].col=b[i].col=0;
        }
        sort(a+1,a+1+m,cmp);
        sort(b+1,b+1+m,cmp);
        krsual(a),krsual(b);
        for(int i=-1000;i<=1000;++i){
            check(i);
            c[i+1000].first=sum;
            c[i+1000].second=num;
        }
        for(int i=0;i<n;++i){
            printf("%d\n",ask(i));
        }
    }
    return 0;
}
上一篇:SQL实例_6给字符分列


下一篇:Leetcode 64 最小路径和