bzoj 1937: [Shoi2004]Mst 最小生成树

题意:

给一个生成树,求最少要更改多少边权,使得这个生成树是最小生成树。

题解:

容易得到,树边只减,非数边只加,不然不忧。
先将给的生成树建出来,那么对于非树边,它的权值最终一定大于等于它连的两点在树上的路径的任意一边。
假设有树边i,非树边j。且i,j在一个环上,有 w[i]di<=w[j]+dj
得到 w[i]w[j]<=di+dj
那么将 w[i]w[j] 看作边权, di,dj 看作KM中的顶标,KM算法有这样一个性质:最大权匹配时顶标和最小(相等子图扩到最大),所以跑KM/最大费用流就可以了。
code:

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
using namespace std;
struct node{
    int x,y,c,d,next,other;
}e[810],a[2000000];int len=0,last[810];
int st,ed;
int n,m;
int map[55][55],fa[55][20],dep[55];
bool check[55][55];
int s[810],p[810],q[810];
bool u[810];
void ins(int x,int y,int c,int d)
{
    int k1=++len;
    a[len].x=x;a[len].y=y;a[len].c=c;a[len].d=d;
    a[len].next=last[x];last[x]=len;
    int k2=++len;
    a[len].x=y;a[len].y=x;a[len].c=0;a[len].d=-d;
    a[len].next=last[y];last[y]=len;
    a[k1].other=k2;
    a[k2].other=k1;
}
bool spfa()
{
    memset(s,-63,sizeof(s));
    memset(u,false,sizeof(u));
    int l=1,r=2;q[l]=st;s[st]=0;u[st]=true;
    while(l!=r)
    {
        int x=q[l];
        for(int i=last[x];i;i=a[i].next)
        {
            int y=a[i].y;
            if(s[y]<s[x]+a[i].d&&a[i].c>0)
            {
                s[y]=s[x]+a[i].d;
                p[y]=i;
                if(!u[y])
                {
                    u[y]=true;
                    q[r]=y;
                    r++;if(r>ed+1) r=1;
                }
            }
        }
        u[x]=false;
        l++;if(l>ed+1) l=1; 
    }
    return s[ed]>0;
}
int flow() 
{
    int x=ed;
    int ans=0,min=-1;
    while(x!=st) 
    {
        int i=p[x];
        if (a[i].c<min||min==-1)min=a[i].c;
        x=a[i].x;
    }
    x=ed;
    while(x!=st) 
    {
        int i=p[x];
        a[i].c-=min;
        a[a[i].other].c+=min;
        x=a[i].x;
        ans=ans+a[i].d*min;
    }
    return ans;
}
void dfs(int x,int f)
{
    fa[x][0]=f;dep[x]=dep[f]+1;
    for(int i=1;(1<<i)<=dep[x];i++)
        fa[x][i]=fa[fa[x][i-1]][i-1];
    for(int i=1;i<=n;i++)
        if(i!=f&&check[x][i]) dfs(i,x);
}
int solve(int x,int y)
{
    if(dep[x]<dep[y]) swap(x,y);
    for(int i=10;i>=0;i--)
        if((1<<i)<=dep[x]-dep[y]) x=fa[x][i];
    if(x==y) return x;
    for(int i=10;i>=0;i--)
        if((1<<i)<=dep[x]&&fa[x][i]!=fa[y][i]) x=fa[x][i],y=fa[y][i];
    return fa[x][0];
}
int main()
{
    scanf("%d %d",&n,&m);
    memset(check,false,sizeof(check));
    for(int i=1;i<=m;i++) scanf("%d %d %d",&e[i].x,&e[i].y,&e[i].c),map[e[i].x][e[i].y]=map[e[i].y][e[i].x]=i;
    for(int i=1;i<n;i++)
    {
        int x,y;scanf("%d %d",&x,&y);
        check[x][y]=check[y][x]=true;
    }
    dep[0]=-1;dfs(1,0);
    st=0;ed=m+1;
    for(int i=1;i<=m;i++)
    {
        int x=e[i].x,y=e[i].y;
        if(check[x][y]) ins(st,i,1,0);
        else
        {
            ins(i,ed,1,0);
            int lca=solve(x,y);
            while(x!=lca)
            {
                int t=map[x][fa[x][0]];
                ins(t,i,1,e[t].c-e[i].c);
                x=fa[x][0];
            }
            while(y!=lca)
            {
                int t=map[y][fa[y][0]];
                ins(t,i,1,e[t].c-e[i].c);
                y=fa[y][0];
            }
        }
    }
    int ans=0;
    while(spfa()) ans+=flow();
    printf("%d",ans);
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值