bzoj3118 Orz the MST(单纯形)

Description

给出一个带权的连通无向图,对于其中的每条边i,在原来边权的基础上,其边权每增加1需要付出的代价为Ai,边权每减少1需要付出的代价为Bi,现在指定该图的一棵生成树,求通过修改边权,使得该生成树成为图的一棵最小生成树,需要付出的最少总代价。
 

Input

第一行两个正整数N, M,表示图的点数和边数,点以1~N编号;
接下来M行,每行六个正整数Ui, Vi, Wi, FFi, Ai, Bi,表示一条边(Ui, Vi)权为Wi,在原边权基础上增加1的边权代价为Ai,减少1的边权代价为Bi,FFi若为1则表示该边在指定的生成树中,若为0表示不在。数据保证FF值为1的边刚好组成原图的一棵生成树。两点之间可能有多条不同的边,但没有连接同一点的边。
 

Output

 
输出一个正整数,表示所需付出的最少总代价。
 

Sample Input

6 8

1 2 3 1 4 2

1 4 2 0 3 4

2 3 5 1 2 1

2 4 4 1 3 5

3 5 2 0 1 3

3 6 1 0 2 4

4 5 7 1 3 2

5 6 5 1 5 4

Sample Output

21

HINT

样例解释:

最优方案为:(1, 4)边权加2,代价6;(3, 5)边权加3,代价3;(3, 6)边权加4,代价8;(4, 5)边权减2,代价4;总代价21。

数据范围:

1<=N<=300, 1<=M, Wi, Ai, Bi<=1000。



[ Submit][ Status][ Discuss]



分析:
线性规划

显然:树边只可能减权,非树边只可能加权

我迟迟没有没有写出不等式的原因就是:树边中最大的一条边比非树边最小的一条边小,但是哪一条是最小边,哪一条是最大边呢
后来我就想,实际上我们没有必要找到确定到底是哪一条边,我们只要把所有非树边和树边都互相比较一下即可

那么就列式子吧:
对于一条树边 wi w i 和一条非树边 wj w j
都有: wixi<=wj+xj w i − x i <= w j + x j
移向得到: wiwj<=xi+xj w i − w j <= x i + x j
这样就可以搞了

写完了之后,样例一直过不去

难道有纰漏?

确实,我一开始的假设就错了:树边中最大的一条边比非树边最小的一条边小
想一下朴素构造最小生成树时,我们虽然是从小到大枚举每一条边
但是如果当前边的两个顶点已经连通,我们就会跳过这条边
因此树中的边不一定都比非树边小

那我们怎么改进我们算法呢?

如果我们已经有了一棵最小生成树,现在要加入一条边(x,y),在什么情况下,这条边会代替生成树中的边呢?
显然,当在树中x—>y的路径中有一条边比当前边的权值大
因此我们要保证路径上的所有边最终权值小于等于这条非树边

按照这个条件构造线性规划矩阵
每个式子的形式应该都是这样的: xi+xj>=wiwj x i + x j >= w i − w j

构造对偶图
用单纯形算法解决即可

tip

约定条件的常数中不要有负数

#include<cstdio>
#include<cstring>
#include<iostream>
#include<cmath> 

using namespace std;

const double INF=1e10;
const double eps=1e-7;
const int N=1005;
double ans=0.0,a[N][N*10],b[N],c[N*10];
int n,m,pre[N],cnt,st[N],tot=0,deep[N],pos[N];
struct node{
    int x,y,v,nxt,id;
};
node way[N];
struct po{
    int x,y,v,type;
};
po e[N];

void add(int i,int u,int w,int z)
{
    tot++;
    way[tot].x=u;way[tot].y=w;way[tot].v=z;way[tot].nxt=st[u];st[u]=tot; way[tot].id=i;
    tot++;
    way[tot].x=w;way[tot].y=u;way[tot].v=z;way[tot].nxt=st[w];st[w]=tot; way[tot].id=i;
}

void priov(int l,int e)
{
    b[l]/=a[l][e];
    for (int i=1;i<=m;i++) if (i!=e)
        a[l][i]/=a[l][e];
    a[l][e]=1/a[l][e];
    for (int i=1;i<=n;i++) if (i!=l&&fabs(a[i][e])>eps)
    {
        b[i]-=b[l]*a[i][e];
        for (int j=1;j<=m;j++) if (j!=e)
            a[i][j]-=a[l][j]*a[i][e];
        a[i][e]=-a[i][e]*a[l][e];
    }
    ans+=c[e]*b[l];
    for (int i=1;i<=m;i++) if (i!=e)
        c[i]-=c[e]*a[l][i];
    c[e]=-c[e]*a[l][e];
}

void simple()
{
    int i,l,e;
    while (1)
    {
        for (i=1;i<=m;i++)
            if (c[i]>eps) break;
        e=i;
        if (e==m+1) break;
        double t=INF;
        for (i=1;i<=n;i++)
            if (a[i][e]>eps&&t>b[i]/a[i][e])
                t=b[i]/a[i][e],l=i;
        if (t==INF) break;
        priov(l,e);
    } 
}

void dfs(int now,int fa,int dep)
{
    deep[now]=dep;
    pre[now]=fa;
    for (int i=st[now];i;i=way[i].nxt)
        if (way[i].y!=fa)
        {
            pos[way[i].y]=way[i].id;   //连接x和fa的边 
            dfs(way[i].y,now,dep+1);
        }
}

void solve(int i,int x,int y)
{
    if (deep[x]<deep[y]) swap(x,y);
    while (deep[x]>deep[y])
    {
        if (e[pos[x]].v>e[i].v)
        {
            cnt++;
            a[i][cnt]++; a[pos[x]][cnt]++;
            c[cnt]=(double)e[pos[x]].v-e[i].v;
        }
        x=pre[x];
    }
    while (x!=y)
    {
        if (e[pos[x]].v>e[i].v)
        {
            cnt++;
            a[i][cnt]++; a[pos[x]][cnt]++;
            c[cnt]=(double)e[pos[x]].v-e[i].v;
        }
        x=pre[x];

        if (e[pos[y]].v>e[i].v)
        {
            cnt++;
            a[i][cnt]++; a[pos[y]][cnt]++;
            c[cnt]=(double)e[pos[y]].v-e[i].v;
        }
        y=pre[y];
    }
}

int main()
{
    scanf("%d%d",&n,&m);
    for (int i=1;i<=m;i++) 
    {
        int A,B;
        scanf("%d%d%d%d%d%d",&e[i].x,&e[i].y,&e[i].v,&e[i].type,&A,&B);
        if (e[i].type) {
            add(i,e[i].x,e[i].y,e[i].v);
            b[i]=(double)B;   //树边只减 
        }
        else b[i]=(double)A;   //非树边只加 
    }
    dfs(1,0,1);

    cnt=0;
    for (int i=1;i<=m;i++) if (e[i].type==0)
        solve(i,e[i].x,e[i].y);

    n=cnt; swap(n,m);       
    simple();
    printf("%.0lf",ans);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值