bzoj 3118: Orz the MST (单纯形)

3118: Orz the MST

Time Limit: 20 Sec   Memory Limit: 256 MB
Submit: 236   Solved: 98
[ Submit][ Status][ Discuss]

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

【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。

Source

[ Submit][ Status][ Discuss]


题解:单纯形

一道不错的单纯形,需要用心想一下。

首先对于树边只可能减少,非树边只可能增加。

我们把树建好,考虑增加一条非树边,在什么情况下可能会替代一条树边,成为新的最小生成树?当非树边的两个端点之间路径上的边有比当前非树边小的时候,可以替换。

所有我们要保证路径上的所有边最终权值小于等于这条非树边。

那么我们可以得到wi-xi<=wj+xj,移项的xi+xj>=wi-wj

然后就得到了多个类似的式子,在满足所有式子的基础上最小化sigma (xi*c[i])

因为我们得到的式子是大于等于,所以需要用到对偶定理来转化求解。

#include<iostream>  
#include<cstdio>  
#include<algorithm>  
#include<cstring>  
#include<cmath>  
#define N 1003  
#define M 4003  
#define eps 1e-8  
using namespace std;  
const double inf=1e20;
int n,m,point[N],tot,nxt[N],v[N],num[N],c1[N],deep[N],val[N],pos[N],fa[N],cnt;  
double ans,v1,b[M],a[N][M],c[M];  
struct data{  
    int u,v,w,a,b,opt;  
}e[N],e1[N];  
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 (fabs(a[i][e])>eps&&i!=l) {  
        b[i]-=b[l]*a[i][e];  
        for (int j=1;j<=m;j++)  
         if (j!=e) a[i][j]-=a[i][e]*a[l][j];  
        a[i][e]=-a[i][e]*a[l][e];  
     }  
    v1+=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];  
}  
double simple()  
{  
    int i,l,e;  
    double t;  
    while (true){  
        for (i=1;i<=m;i++)   
         if (c[i]>eps) break;  
        e=i;  
        if (e==m+1) return v1;  
        t=inf;  
        for (int 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) return inf;  
        priov(l,e);  
    }  
}  
void add(int x,int y,int z,int k)  
{  
    tot++; nxt[tot]=point[x]; point[x]=tot; v[tot]=y; c1[tot]=z; num[tot]=k;  
    tot++; nxt[tot]=point[y]; point[y]=tot; v[tot]=x; c1[tot]=z; num[tot]=k;  
    //cout<<x<<" "<<y<<" "<<z<<endl;  
}  
void dfs(int x,int f)  
{  
    deep[x]=deep[f]+1;  
    for (int i=point[x];i;i=nxt[i]){  
        if (v[i]==f) continue;  
        val[v[i]]=c1[i]; pos[v[i]]=num[i];  
        fa[v[i]]=x;  
        dfs(v[i],x);  
    }  
}  
void solve(int x,int y,int i)  
{  
    while (x!=y) {  
        if (deep[x]>deep[y]) {  
            if (val[x]>e[i].w) {  
                ++cnt; a[i][cnt]=1; a[pos[x]][cnt]=1;  
                //cout<<i<<" "<<pos[x]<<endl;  
                c[cnt]=val[x]-e[i].w;  
            }  
            x=fa[x];  
        }  
        else {  
            if (val[y]>e[i].w) {  
                ++cnt; a[i][cnt]=1; a[pos[y]][cnt]=1;  
                //cout<<i<<" "<<pos[y]<<endl;  
                c[cnt]=val[y]-e[i].w;  
            }  
            y=fa[y];  
        }  
    }  
}  
int main()  
{  
    freopen("a.in","r",stdin);  
    freopen("my.out","w",stdout);
    scanf("%d%d",&n,&m);  
    for (int i=1;i<=m;i++) {  
       scanf("%d%d%d%d%d%d",&e[i].u,&e[i].v,&e[i].w,&e[i].opt,&e[i].a,&e[i].b);  
       if (e[i].opt) add(e[i].u,e[i].v,e[i].w,i),b[i]=e[i].b;  
       else b[i]=e[i].a;  
    }  
    dfs(1,0); 
    cnt=0;  
    for (int i=1;i<=m;i++) {  
        if (e[i].opt) continue;  
        solve(e[i].u,e[i].v,i);  
    }  
    n=cnt; swap(n,m);  
    /*for (int i=1;i<=m;i++) cout<<b[i]<<" "; cout<<endl;
	for (int i=1;i<=n;i++) cout<<c[i]<<" "; cout<<endl;
	for (int i=1;i<=m;i++,cout<<endl)
	  for (int j=1;j<=n;j++) cout<<a[i][j]<<" ";
	cout<<endl; */
    ans=simple();  
    printf("%.0lf\n",ans);  
}  



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值