[BZOJ3118]Orz the MST(单纯形)

题目描述

传送门

题解

这题我的方法好蠢啊
xi 表示第i条边增加了几次, yi 表示第i条边减少了几次,那么目标函数就是最小化 Aixi+Biyi
然后对于每一条非树边,它的两个端点对应的树链上的每一条边都应该小于这条非树边,也就是说, xiyi+wixjyj+wj ,因为 wi wj 的大小不定,但是 Ai Bi 一定是正数,所以要将矩阵对偶来满足初始可行解。也就是化成 xjxiyj+yiwiwj 的形式
然后用单纯形解就行了
但是实际上非树边只会增大,树边只会减小,所以一条边设一个未知量就行了,gg
感觉最坏情况下是可以卡成 m2 的啊,但是这题没有卡

代码

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
using namespace std;

const double eps=1e-9;
const double inf=1e18;
int N,M,U[1005],V[1005],W[1005],A[1005],B[1005],FF[1005];
int tot,point[305],nxt[2005],v[2005],num[2005],h[205],father[305],last[305];
int n,m;
double a[4005][4005],b[4005],c[4005],ans;

void add(int x,int y,int id)
{
    ++tot; nxt[tot]=point[x]; point[x]=tot; v[tot]=y; num[tot]=id;
}
void dfs(int x,int fa)
{
    h[x]=h[fa]+1;father[x]=fa;
    for (int i=point[x];i;i=nxt[i])
        if (v[i]!=fa)
        {
            last[v[i]]=num[i];
            dfs(v[i],x);
        }
}
void build(int x,int y,int id)
{
    int jd;
    // xj-xi-yj+yi<=wi-wj
    if (h[x]<h[y]) swap(x,y);
    while (h[x]>h[y])
    {
        jd=last[x];
        ++n;
        a[jd][n]=-1.0;a[id][n]=1.0;a[jd+M][n]=1.0;a[id+M][n]=-1.0;
        c[n]=W[jd]-W[id]+0.0;
        x=father[x];
    }
    while (x!=y)
    {
        jd=last[x];
        ++n;
        a[jd][n]=-1.0;a[id][n]=1.0;a[jd+M][n]=1.0;a[id+M][n]=-1.0;
        c[n]=W[jd]-W[id]+0.0;
        x=father[x];

        jd=last[y];
        ++n;
        a[jd][n]=-1.0;a[id][n]=1.0;a[jd+M][n]=1.0;a[id+M][n]=-1.0;
        c[n]=W[jd]-W[id]+0.0;
        y=father[y];
    }
}
void pivot(int l,int e)
{
    b[l]/=a[l][e];
    for (int i=1;i<=n;++i)
        if (i!=e) a[l][i]/=a[l][e];
    a[l][e]=1.0/a[l][e];
    for (int i=1;i<=m;++i)
        if (i!=l&&fabs(a[i][e])>eps)
        {
            b[i]-=a[i][e]*b[l];
            for (int j=1;j<=n;++j)
                if (j!=e)
                    a[i][j]-=a[i][e]*a[l][j];
            a[i][e]=-a[i][e]*a[l][e];
        }
    ans+=b[l]*c[e];
    for (int i=1;i<=n;++i)
        if (i!=e)
            c[i]-=c[e]*a[l][i];
    c[e]=-c[e]*a[l][e];
}
void simplex()
{
    while (1)
    {
        int e;
        for (e=1;e<=n;++e)
            if (c[e]>eps) break;
        if (e==n+1) break;
        double Min=inf,t;int l;
        for (int i=1;i<=m;++i)
            if (a[i][e]>eps&&(t=b[i]/a[i][e])<Min)
            {
                Min=t;
                l=i;
            }
        pivot(l,e);
    }
}
int main()
{
    scanf("%d%d",&N,&M);
    for (int i=1;i<=M;++i)
    {
        scanf("%d%d%d%d%d%d",&U[i],&V[i],&W[i],&FF[i],&A[i],&B[i]);
        if (FF[i]) add(U[i],V[i],i),add(V[i],U[i],i);
    }
    dfs(1,0);
    m=M*2;
    for (int i=1;i<=M;++i)
        b[i]=A[i]+0.0,b[i+M]=B[i]+0.0;
    for (int i=1;i<=M;++i)
        if (!FF[i]) build(U[i],V[i],i);
    simplex();
    printf("%.0lf\n",ans);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值