9.28 无向图填数

题意

给定一个无向图,每条边有边权(保证边权为正),有部分点有点权

请你在没有点权的点上填数(实数域内)

使得下列式子最小:
\[ \sum_{(u,v)\in E} w(u,v)\times (v_u-v_v)^2 \]

解法

考试时想得已经很接近了。。可惜没继续深入

对于一个点权不确定的点,如果与其有边直接相连的点的点权都确定了,那么它的点权也可以被确定

设与其相连的边的边权为\(c_i\),与其相连的点的点权为\(x_i\)

当前的点的点权选取只对其相连的边有影响,那么我们的目的就是最小化下面的式子
\[ \sum_{c \in E_u} c_i\times(x-x_i)^2 \]
把它拆开
\[ \begin{aligned} &= \sum c_ix^2-2c_ix_ix+c_ix_i^2 \\ &= (\sum c_i)x^2-2(\sum c_ix_i)x+c_ix_i^2 \end{aligned} \]
可以发现以上是一个二次函数的形式

由于保证边权为正,二次函数的开口一定向上

那么当\(x=\frac{\sum c_ix_i}{\sum c_i}\)时,这个式子取到最小值

这样我们就解决了相邻点的点权确定的情况下如何确定一个点点权的选取

但显然一个点的相邻点的点权并不一定是全部确定的

于是我们把每个未知点看做一个未知数,放在方程左边;已知点的贡献移到方程的右边

列出若干组方程高斯消元求解即可

至于方程的系数,根据上面推出的式子\(x=\frac{\sum c_ix_i}{\sum c_i}\)就能确定


代码

#include <cstdio>
#include <iostream>

using namespace std;

const double eps = -1e12;

struct edge { int u, v, w; } e[30010];

int n, m, cnt;
int sum[510], id[510], pos[510];

double val[510], f[510][510];

inline double sqr(double x) { return x * x; }
inline double is(double x)  { return x < eps; }

void Guass() {
    
    for (int i = 1; i < cnt; ++i) {
        int pos = i;
        while (pos < cnt && is(f[pos][i]))  ++pos;
        swap(f[i], f[pos]);
        for (int j = 1; j < cnt; ++j) {
            if (i == j)  continue;
            double tmp = f[j][i] / f[i][i];
            for (int k = i; k <= cnt; ++k)  f[j][k] -= tmp * f[i][k];
        }
    }
    
    for (int i = 1; i < cnt; ++i)  val[pos[i]] = f[i][cnt] / f[i][i];
}

int main() {
    
    scanf("%d%d", &n, &m);
    
    int u, v, w;    
    for (int i = 1; i <= m; ++i) {
        scanf("%d%d%d", &u, &v, &w);
        e[i] = (edge){u, v, w};
    }
    
    for (int i = 1; i <= n; ++i) {
        scanf("%lf", val + i);
        if (val[i] == -1)  id[i] = ++cnt, pos[cnt] = i;
    }
    
    ++cnt;
    for (int i = 1; i <= m; ++i) {
        u = e[i].u, v = e[i].v, w = e[i].w;
        if (u == v)  continue;
        if (id[u]) {
            sum[u] += w;
            if (id[v])  
                f[id[u]][id[v]] += w;   
            else
                f[id[u]][cnt] -= 1.000000 * val[v] * w;
        }
        if (id[v]) {
            sum[v] += w;
            if (id[u])
                f[id[v]][id[u]] += w;
            else
                f[id[v]][cnt] -= 1.000000 * val[u] * w;
        }
    }
    
    for (int i = 1; i <= n; ++i)
        if (id[i])  f[id[i]][id[i]] -= sum[i];
        
    Guass();    
    
    double ans = 0;
    for (int i = 1; i <= m; ++i)
        ans += e[i].w * sqr(val[e[i].u] - val[e[i].v]);
    
    printf("%.10lf\n", ans);
    
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/VeniVidiVici/p/11605976.html