码迷,mamicode.com
首页 > 其他好文 > 详细

@bzoj - 2654@ tree

时间:2019-01-03 16:43:32      阅读:217      评论:0      收藏:0      [点我收藏+]

标签:opera   ini   accept   printf   toc   tps   定义   否则   NPU   

目录


@description@

给你一个无向带权连通图,每条边是黑色或白色。让你求一棵最小权的恰好有 need 条白色边的生成树。
题目保证有解。

input
第一行V, E, need分别表示点数,边数和需要的白色边数。
接下来 E 行,每行 s, t, c, col 表示这边的端点(点从0开始标号),边权,颜色(0白色,1黑色)。
output
一行表示所求生成树的边权和。
V <= 50000, E<=100000, 所有数据边权为[1, 100]中的正整数。

sample Input
2 2 1
0 1 1 1
0 1 2 0
sample output
2

@solution@

dp 凸优化的起源……尽管这道题并不是 dp……
事实上这个算法有三个名字:WQS 二分,dp 凸优化,带权二分。
大家可以去看看这一位 dalao 的博客

@part - 1@

一个直观的想法是,我们先用 K 条白边生成一棵最小生成树,再把黑边加进去。
然而好像是能被卡掉的。所以我们不能这么搞。

那怎么去限制白边数量呢?我们给每条白边二分一个附加权值,再生成最小生成树,求出白边数量调整二分。
可以从直观上感性理解:假如这个附加权值越大,则我们选取的白边会越少,反之会越大。因此附加权值与白边数量的关系是单调的,我们就可以玩二分了。

最后从这个生成树中减去附加权值即可。

@part - 2@

然而这个算法……看起来并不是很严谨啊。
会不会出现一种情况:二分的附加权值稍稍大一点白边数量就不够,二分的附加权值稍稍小一点白边数量又多出来了。总之就是二分不到题目说的 K 条。
以及:这个附加权值可以为小数吗?我二分需要管它的精度问题吗?

好的。我们慢慢来。
先定义一个函数 \(f(x)\) 表示当我选取 \(x\) 条白边的时候,求得的最小生成树权值。
经过 dalao 们的打表严格证明,我们可以得到 \(f(x)\) 是下凸函数。
并且,我们也可以求出 \(f(x)\) 的最小值(原图中的最小生成树)。

好,我们再来看我们刚刚的算法干了什么。我们二分了一个附加权值 \(k\),并把所有白边都加上了这个附加权值,得到一个新函数 \(g(x) = f(x)+k*x\)
然后我们找到这个新函数的最小值(新图中的最小生成树)。求出它对应的横坐标(生成树所含的白边数量),并以此调整二分。

上面我所提到的那位 dalao 提出了一种理解该算法的方法:我们对 \(f(x)\)\(g(x)\) 分别求导,得到 \(f‘(x)\)\(g‘(x)\)
因为 \(f(x)\) 是下凸的,所以 \(f‘(x)\) 是单增的,同时 \(f‘(x)\) 的零点对应着 \(f(x)\) 的最小值。
可以得到 \(g‘(x) = f‘(x) + k\),即将 \(f‘(x)\) 平移 \(k\) 个单位。这样就可以说明该算法的正确性了。
同时,因为 \(f(x)\) 上的点都是整数,所以 \(f‘(x)\) 上的点也应该都是整数,所以我们不需要进行小数二分。

@accepted code@

#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
const int MAXN = 50000;
struct edge{
    int u, v, w;
    edge(int _u=0, int _v=0, int _w=0):u(_u), v(_v), w(_w){}
};
bool operator < (edge a, edge b) {
    return a.w < b.w;
} 
vector<edge>edges[2];
void init() {
    for(int i=0;i<2;i++)
        edges[i].clear();
}
int fa[MAXN + 5];
int find(int x) {
    return (fa[x] == x) ? x : fa[x] = find(fa[x]);
}
int N, M, K, ans, cnt, p, q;
bool check(int x) {
    ans = cnt = p = q = 0;
    for(int i=1;i<=N;i++)
        fa[i] = i;
    while( p < edges[0].size() && q < edges[1].size() ) {
        if( find(edges[0][p].u) == find(edges[0][p].v) )
            p++;
        else if( find(edges[1][q].u) == find(edges[1][q].v) )
            q++;
        else {
            if( edges[0][p].w + x < edges[1][q].w ) {
                ans += edges[0][p].w + x;
                fa[find(edges[0][p].u)] = find(edges[0][p].v);
                cnt++; p++;
            }
            else {
                ans += edges[1][q].w;
                fa[find(edges[1][q].u)] = find(edges[1][q].v);
                q++;
            }
        }
    }
    while( p < edges[0].size() ) {
        if( find(edges[0][p].u) == find(edges[0][p].v) )
            p++;
        else {
            ans += edges[0][p].w + x;
            fa[find(edges[0][p].u)] = find(edges[0][p].v);
            cnt++; p++;
        }
    }
    while( q < edges[1].size() ) {
        if( find(edges[1][q].u) == find(edges[1][q].v) )
            q++;
        else {
            ans += edges[1][q].w;
            fa[find(edges[1][q].u)] = find(edges[1][q].v);
            q++;
        }
    }
    return cnt <= K;
}
int main() {
    init();
    scanf("%d%d%d", &N, &M, &K);
    for(int i=1;i<=M;i++) {
        int a, b, c, x;
        scanf("%d%d%d%d", &a, &b, &c, &x);
        edges[x].push_back(edge(a + 1, b + 1, c));
    }
    for(int i=0;i<2;i++)
        sort(edges[i].begin(), edges[i].end());
    int le = -100, ri = 100;
    while( le < ri ) {
        int mid = (le + ri) >> 1;
        if( check(mid) ) ri = mid;
        else le = mid + 1;
    }
    check(le);
    printf("%d\n", ans - K*le);
}

@details@

事实证明,这道题 bzoj 的数据非常的水。
我带权二分的时候连那个二分出来的附加权值都没有加到边权里去。
然后在 bzoj 上它竟然 AC 了?

建议大家如果要检测自己代码的正确性,去 hdu4253 (需要写多组数据)。
否则你连自己代码有错都不知道,一懵一懵地好像自己就 AC 了。

@bzoj - 2654@ tree

标签:opera   ini   accept   printf   toc   tps   定义   否则   NPU   

原文地址:https://www.cnblogs.com/Tiw-Air-OAO/p/10214533.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!