codeforces 733F. Drivers Dissatisfaction

5 篇文章 0 订阅
3 篇文章 0 订阅

题目链接http://codeforces.com/contest/733/problem/F
题目大意:给一张n个点m条边的连通图,每条边(ai,bi)有一个权值wi和费用ci,表示这条边每降低1的权值需要ci的花费。现在一共有S费用可以用来降低某些边的权值(可以降到负数),求图中的一棵权值和最小的生成树并输出方案。
数据范围:2 ≤ n ≤ 2·10^5, n - 1 ≤ m ≤ 2·10^5, 1 ≤ wi, ci, S ≤ 10^9

题解:感觉我已经在水题的道路上一去不复返了hhhh。由于wi可以是负数,因此对于一棵生成树,我们只要把S全部用在ci最小的边即可。求出原图中的一棵最小生成树以及树中ci最小的边,计算出当前权值和。枚举每条不在生成树上的边 j (cj < ci),用lca求出aj到bj路径上w最大的边,用wj替换它然后计算出权值和,如果比答案小就更新答案。最后输出方案即可。
时间复杂度O(nlogn+mlogn)

代码如下:

#include <algorithm>
#include <cstdio>
const int N=200005;
int f[N][20],g[N][20],fa[N],w[N],c[N],a[N],b[N],id[N],fi[N],
    to[N*2],ne[N*2],v[N*2],dep[N],u[N],n,m,tot,mn;
long long sum,ans;
bool cmp(int x,int y){return w[x]<w[y];}
int find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);}
void add(int x,int y,int z){
    to[++tot]=y;v[tot]=z;ne[tot]=fi[x];fi[x]=tot;
}
void dfs(int x){
    dep[x]=dep[f[x][0]]+1;
    for (int i=f[x][0],j=0;f[i][j];i=f[i][j++]){
        f[x][j+1]=f[i][j];
        if (w[g[i][j]]>w[g[x][j]]) g[x][j+1]=g[i][j];
            else g[x][j+1]=g[x][j];
    }
    for (int i=fi[x];i;i=ne[i])
    if (to[i]!=f[x][0]){
        f[to[i]][0]=x;
        g[to[i]][0]=v[i];
        dfs(to[i]);
    }
}
int lca(int x,int y){
    int z=0;
    if (dep[x]<dep[y]) std::swap(x,y);
    for (int i=17;dep[x]>dep[y];x=f[x][i]){
        for (;i && dep[f[x][i]]<dep[y];i--);
        if (w[g[x][i]]>w[z]) z=g[x][i];
    }
    for (int i=17;x!=y;x=f[x][i],y=f[y][i]){
        for (;i && f[x][i]==f[y][i];i--);
        if (w[g[x][i]]>w[z]) z=g[x][i];
        if (w[g[y][i]]>w[z]) z=g[y][i];
    }
    return z;
}
int main(){
    scanf("%d%d\n",&n,&m);
    for (int i=1;i<=m;i++) scanf("%d",&w[i]);
    for (int i=1;i<=m;i++) scanf("%d",&c[i]);
    for (int i=1;i<=m;i++) scanf("%d%d",&a[i],&b[i]);
    for (int i=1;i<=m;i++) id[i]=i;
    std::sort(id+1,id+m+1,cmp);
    for (int i=1;i<=n;i++) fa[i]=i;
    sum=0;c[mn=0]=2000000000;
    for (int i=1,j=id[1];i<=m;j=id[++i])
    if (find(a[j])!=find(b[j])){
        fa[find(a[j])]=find(b[j]);
        sum+=w[j];u[j]=1;
        add(a[j],b[j],j);
        add(b[j],a[j],j);
        if (c[j]<c[mn]) mn=j;
    }
    long long S;
    scanf("%I64d\n",&S);
    ans=sum-S/c[mn];
    dfs(1);
    int minc=c[mn];
    for (int i=1;i<=m;i++)
    if (c[i]<minc){
        int x=lca(a[i],b[i]);
        long long cur=sum+w[i]-w[x]-S/c[i];
        if (cur<ans) ans=cur,mn=i;
    }
    printf("%I64d\n",ans);
    w[mn]-=S/c[mn];u[lca(a[mn],b[mn])]=0;u[mn]=1;
    for (int i=1;i<=m;i++)
        if (u[i]) printf("%d %d\n",i,w[i]);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值