bzoj-3118 Orz the MST

141 篇文章 0 订阅
75 篇文章 0 订阅

题意:

给出一个无向连通图,并指定其中一颗生成树;

每条边上有一个权值vali,如果增大这个权值1则花费Ai,减小1花费Bi;

现在要使指定的生成树为一颗最小生成树,求最小花费;

n<=300,m<=1000;


题解:

一道线性规划比较神的题目,前面刷的比较偏水就不刷了;

首先有一点极为显然的东西(我居然没看出来),树上的边一定减小权值,非树上的边一定增大权值;

然后考虑对于一颗生成树要为最小要满足的条件,也就是本题的约束条件;

如同Tarjan算法一样,每一条非树边都会在树上生成一个环;

而如果这个环上的某个边权值比它大,那么这个环就可以从那条边处断开,且生成树更小;

也就是说对于一个非树边,环上边都要小于等于它;

找约束的过程我写的似乎比较蠢,深搜记了一堆再LCA;

不过无论如何最坏也不过n*m总归不会因此TLE;

设xi为第i条边的改变量,那么树边的即为减小量,非树边的为增大量;

可得方程:vali+xi<=valj-xj   (i为树边j为非树边,且i,j在一个环上);

移项:xi+xj<=valj-vali;

现在的线性规划为:

Min∑costi*xi

xi+xj<=valj-vali

显然它不存在基本可行解,那么做一些变形;

首先将目标函数取反:

Max∑ -costi*xi

xi+xj<=valj-vali

然后对偶原理!

Max∑(valj-vali)*x(j,i)

%*&^%*>=-costi

再对不等式变号:

Max∑(valj-vali)*x(j,i)

-%*&^%*<=costi

现在就可以发现,线性规划已经是标准型了;

而题中的cost都是正数,也就是这个线性规划有基本可行解咯;

然后上单纯型直接搞这东西,题目性质也保证了这东西不会无界;

时间复杂度O(单纯型(n*m,m));

显然是会T死的,然而约束并不会有最坏情况n*m这么多。。

所以还是可以过的啦,n*m的数组开到4000能A;


代码:


#include<vector> 
#include<math.h> 
#include<stdio.h> 
#include<string.h> 
#include<algorithm> 
#define N 310 
#define M 1100 
using namespace std; 
const double EPS = 1e-8; 
const double INF = 1e100; 
struct node 
{ 
    int n, m; 
    double a[M][M << 2], b[M], c[M << 2], v; 
    int find() 
    { 
        for (int i = 1; i <= n; i++) 
        { 
            if (c[i] > EPS) 
                return i; 
        } 
        return 0; 
    } 
    void Rotate(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 / a[l][e]; 
        for (int i = 1; i <= m; i++) 
        { 
            if (i == l || fabs(a[i][e]) < EPS)   continue; 
            b[i] -= b[l] * a[i][e]; 
            for (int j = 1; j <= n; j++) 
            { 
                if (j != e) 
                    a[i][j] -= a[l][j] * a[i][e]; 
            } 
            a[i][e] *= -a[l][e]; 
        } 
        v += c[e] * b[l]; 
        for (int i = 1; i <= n; i++) 
        { 
            if (i != e) 
                c[i] -= c[e] * a[l][i]; 
        } 
        c[e] *= -a[l][e]; 
    } 
    void Simplex() 
    { 
        int i, j, k, l, e; 
        while (e = find()) 
        { 
            double lim = INF; 
            for (i = 1; i <= m; i++) 
            { 
                if (a[i][e] < EPS)   continue; 
                if (lim>b[i] / a[i][e]) 
                    lim = b[i] / a[i][e], l = i; 
            } 
            Rotate(l, e); 
        } 
    } 
}T; 
vector<int>to[N], val[N], cost[N], no[N]; 
vector<bool>cov[N]; 
int fa[N], prec[N], prev[N], preno[N], tim[N], deep[N], tot; 
void Build(int x, int y, int val, int no) 
{ 
    if (deep[x] < deep[y]) 
        swap(x, y); 
    while (deep[x]>deep[y]) 
    { 
        T.c[++T.n] = -(val - prev[x]); 
        T.a[no][T.n] = 1; 
        T.a[preno[x]][T.n] = 1; 
        x = fa[x]; 
    } 
    while (x != y) 
    { 
        T.c[++T.n] =-( val - prev[x]); 
        T.a[no][T.n] = 1; 
        T.a[preno[x]][T.n] = 1; 
        x = fa[x]; 
        T.c[++T.n] = -(val - prev[y]); 
        T.a[no][T.n] = 1; 
        T.a[preno[y]][T.n] = 1; 
        y = fa[y]; 
    } 
} 
void dfs(int x, int pre, int v, int num, int d) 
{ 
    int i, y; 
    fa[x]=pre,prev[x] = v, preno[x] = num, tim[x] = ++tot, deep[x] = d; 
    for (i = 0; i < to[x].size(); i++) 
    { 
        if (cov[x][i]&&(y=to[x][i])!=pre) 
            dfs(y, x, val[x][i], no[x][i], d + 1); 
    } 
    for (i = 0; i < to[x].size(); i++) 
    { 
        if (!cov[x][i] && tim[y = to[x][i]] && tim[y] < tim[x]) 
        { 
            Build(x, y, val[x][i], no[x][i]); 
        } 
    } 
} 
int main() 
{ 
    int n, m, i, j, k, x, y, v, f, a, b; 
    scanf("%d%d", &n, &m); 
    for (i = 1; i <= m; i++) 
    { 
        scanf("%d%d%d%d%d%d", &x, &y, &v, &f, &a, &b); 
        to[x].push_back(y), val[x].push_back(v), cov[x].push_back(f), no[x].push_back(i); 
        to[y].push_back(x), val[y].push_back(v), cov[y].push_back(f), no[y].push_back(i); 
        if (f) 
            T.b[i] = b; 
        else
            T.b[i] = a; 
    } 
    dfs(1, 0, 0, 0, 1); 
    T.m=m; 
    T.Simplex(); 
    printf("%.0lf",T.v); 
    return 0; 
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值