hdu 6982 / 2021“MINIEYE杯”中国大学生算法设计超级联赛(3)1010 Road Discount(二分+最小生成树)

https://acm.hdu.edu.cn/showproblem.php?pid=6982

 

题意:

n个城市要修n-1条道路使他们联通,有m条道路可以修,价格有原价和折扣价

问最多可以选k个折扣价时的最小花费

对于k∈[0,n-1]依次回答

 

做这道题,得先会做“边有黑白两色,求恰好有k条白边的最小生成树”

可以看这里https://www.cnblogs.com/TheRoadToTheGold/p/15131583.html

 

这上面的基础上,题目转化为

原价就是黑边,折扣价就是白边

对于k∈[0,n-1],求“边有黑白两色,求恰好有k条白边的最小生成树”

直接枚举k用所有边求最小生成树会超时

要2个优化

 

优化一:

因为边权<=1000,提前对所有加边权的方式求解,记录使用的白边条数和总权值

这样二分的时候直接用即可

做最小生成树的次数为2000次

不提前求解的话,做最小生成树的次数为n*log(2000) 大约为10000次

 

优化二:

边数太多了

结论:在黑白两种边的最小生成树中的边,一定属于只有白边的最小生成树或者只有黑边的最小生成树

这样可以把边数优化到与n同级

因为

假设存在一条 黑白两种边的最小生成树中的边e,既不属于只有白边的最小生成树又不属于只有黑边的最小生成树

把这条边e去掉,会把树分为两部分

再去对应e的颜色中的最小生成树上,用连接这两部分的边E替换e

那么含E的最小生成树权值一定<=含e的最小生成树权值

 

#include<bits/stdc++.h>

using namespace std;

#define N 1001
#define M 400001

int n,m,mm;
struct node
{
    int u,v,w,c;
}f[M],e[N<<1];

int use,sum;

int fa[N];

int fuse[N*2+10],fsum[N*2+10];

bool cmp(node p,node q)
{
    if(p.w!=q.w) return p.w<q.w;
    return p.c<q.c;
}

int find(int i) { return fa[i]==i ? i : fa[i]=find(fa[i]); }

void check(int x)
{
    for(int i=1;i<=m;++i) 
        if(!e[i].c) e[i].w+=x;
    sort(e+1,e+m+1,cmp);
    for(int i=1;i<=n;++i) fa[i]=i;
    int now=0,fu,fv,j=1;
    use=sum=0;
    while(now!=n-1)
    {
        fu=find(e[j].u);
        fv=find(e[j].v);
        if(fu!=fv)
        {
            now++;
            fa[fu]=fv;
            if(!e[j].c) ++use;
            sum+=e[j].w;
        }
        ++j;
    }
    for(int i=1;i<=m;++i) 
        if(!e[i].c) e[i].w-=x;
}

void pre_edge(int col)
{
    int now=0,j=1,fu,fv;
    for(int i=1;i<=n;++i) fa[i]=i;
    sort(f+1,f+m+1,cmp);
    while(now!=n-1)
    {
        if(f[j].c==col)
        {
            fu=find(f[j].u);
            fv=find(f[j].v);
            if(fu!=fv)
            {
                now++;
                fa[fu]=fv;
                e[++mm]=f[j];
            }            
        }
        ++j;
    }
}

int main()
{
    int T;
    int l,r,mid,ans;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d%d",&n,&m);
        for(int i=1;i<=m;++i) 
        {
            scanf("%d%d%d",&f[i*2-1].u,&f[i*2-1].v,&f[i*2-1].w);
            f[i*2-1].c=1;
            f[i*2].u=f[i*2-1].u;
            f[i*2].v=f[i*2-1].v;
            scanf("%d",&f[i*2].w);
            f[i*2].c=0;
        }
        m<<=1;
        mm=0;
        pre_edge(1);
        pre_edge(0);
        m=mm; 
        for(int k=-1001;k<=1001;++k) 
        {
            check(k);
            fsum[k+1001]=sum;
            fuse[k+1001]=use;
        }
        for(int k=0;k<n;++k)
        {
            l=-1001;
            r=1001;
            while(l<=r)
            {
                mid=l+r>>1;
                if(fuse[mid+1001]>=k) 
                {
                    ans=fsum[mid+1001]-k*mid;
                    l=mid+1;
                } 
                else r=mid-1;
            }
            printf("%d\n",ans); 
        }
    }
}

 

posted @ 2021-08-12 11:23  TRTTG  阅读(75)  评论(0编辑  收藏  举报