6041 I Curse Myself(点双联通加集合合并求前K大) 2017多校第一场

题意:

给出一个仙人掌图,然后求他的前K小生成树。

思路:

先给出官方题解

由于图是一个仙人掌,所以显然对于图上的每一个环都需要从环上取出一条边删掉。所以问题就变
为有 M 个集合,每个集合里面都有一堆数字,要从每个集合中选择一个恰好一个数加起
来。求所有的这样的和中,前 K 大的是哪些。这就是一个经典问题了。

点双联通就不说了 都一眼能看出来做法就是缩点之后每个环每次取一个,然后找最大的k个所以这道题的难点就在这里,做法当然是不知道啦,看了题解和博客才懂的。以前做过两个集合合并的,这个是k个合并,和两个集合合并差不多,不过感觉很厉害啊。。就是先两个合并,然后和第三个合并,在和第四个,然后就可以了。。复杂度的话题解说的O(KM)。。。那就O(KM)吧,反正这个是真不会证过程挺厉害的 在合并的时候

void merge(vector<int> &V , vector<int> B) {
   priority_queue< opt > Q;
   for (int i = 0 ; i < B.size() ; ++ i) {
       Q.push((opt) {V[0] + B[i] , i , 0});
   }
   W.resize(0);
   while (W.size() < K && !Q.empty()) {
       auto it = Q.top(); Q.pop();
       W.push_back(it.w);
       if (it.y + 1 < V.size()) {
           ++ it.y;
           Q.push((opt) {B[it.x] + V[it.y] , it.x , it.y});
       }
   }
   V = W;
}

最开始一直没看懂  看了一天呀。。。  后来想通了感觉还是挺简单的,然后在本机跑,没开O2跑了大半辈子。。。

具体过程其实就是对于每个已经合并了的数组,最开始让他最大的和B数组合并,然后对于已经合并了的数组,如果当前

的这一位被push进答案数组,那么就把先合并数组的次大的和B数组的当前数字合并就可以了。

思路挺厉害的~

代码就贴标程的吧~

#include <bits/stdc++.h>
using namespace std;
const int N = 1005;
int n , m , K;

struct opt {
    int w , x , y;
    bool operator < (const opt& R) const {
        return w < R.w;
    }
};

vector<int> W;
void merge(vector<int> &V , vector<int> B) {
    priority_queue< opt > Q;
    for (int i = 0 ; i < B.size() ; ++ i) {
        Q.push((opt) {V[0] + B[i] , i , 0});
    }
    W.resize(0);
    while (W.size() < K && !Q.empty()) {
        auto it = Q.top(); Q.pop();
        W.push_back(it.w);
        if (it.y + 1 < V.size()) {
            ++ it.y;
            Q.push((opt) {B[it.x] + V[it.y] , it.x , it.y});
        }
    }
    V = W;
}

int pre[N] , mcnt;
struct edge {
    int x , w , next;
} e[N << 2];

vector<int> res;

int dfn[N] , low[N] , ncnt;
stack<int> S;

void dfs(int x , int fa) {
    dfn[x] = low[x] = ++ ncnt;
    for (int i = pre[x] ; ~i ; i = e[i].next) {
        int y = e[i].x;
        if (!dfn[y]) {
            S.push(i);
            dfs(y , i ^ 1);
            low[x] = min(low[x] , low[y]);
            if (low[y] > dfn[x]) {}//(x , y) is bridge
            if (low[y] >= dfn[x]) {
                int j;
                vector<int> V;
                do {
                    j = S.top();
                    S.pop();
                    V.push_back(e[j].w);
                } while (j != i);
                if (V.size() > 1) {
                    //cout << V.size() << endl;
                    //for (auto &x : V) cout << x << ‘ ‘; cout << endl;
                    merge(res , V);
                }
            }
        } else if (i != fa && dfn[y] < dfn[x])
            S.push(i) , low[x] = min(low[x] , dfn[y]);
    }
}

void work() {
    memset(pre , -1 , sizeof(pre));
    mcnt = ncnt = 0;
    int sum = 0;
    for (int i = 0 ; i < m ; ++ i) {
        int x , y , z;
        scanf("%d%d%d" , &x , &y , &z);
        e[mcnt] = (edge) {y , z , pre[x]} , pre[x] = mcnt ++;
        e[mcnt] = (edge) {x , z , pre[y]} , pre[y] = mcnt ++;
        sum += z;
    }
    scanf("%d" , &K);
    res.resize(0);
    res.push_back(0);
    memset(dfn , 0 , sizeof(dfn));
    dfs(1 , 0);
    int w = 0;
    for (int i = 0 ; i < res.size() ; ++ i) {
        w += (i + 1) * (sum - res[i]);
    }
    static int ca = 0;
    printf("Case #%d: %u\n" , ++ ca , w);
}

int main() {
    res.reserve(100001);
    W.reserve(100001);
    while (~scanf("%d%d" , &n , &m)) {
        work();
    }
    return 0;
}
时间: 2024-12-20 13:05:02

6041 I Curse Myself(点双联通加集合合并求前K大) 2017多校第一场的相关文章

hihocoder #1190 : 连通性&#183;四 点双联通分量

http://hihocoder.com/problemset/problem/1190?sid=1051696 先抄袭一下 时间限制:10000ms 单点时限:1000ms 内存限制:256MB 描述 小Hi和小Ho从约翰家回到学校时,网络所的老师又找到了小Hi和小Ho. 老师告诉小Hi和小Ho:之前的分组出了点问题,当服务器(上次是连接)发生宕机的时候,在同一组的服务器有可能连接不上,所以他们希望重新进行一次分组.这一次老师希望对连接进行分组,并把一个组内的所有连接关联的服务器也视为这个组内

POJ 3177 Redundant Paths(边双联通图)

Description In order to get from one of the F (1 <= F <= 5,000) grazing fields (which are numbered 1..F) to another field, Bessie and the rest of the herd are forced to cross near the Tree of Rotten Apples. The cows are now tired of often being forc

边双联通问题求解(构造边双连通图)POJ3352(Road Construction)

题目链接:传送门 题目大意:给你一副无向图,问至少加多少条边使图成为边双联通图 题目思路:tarjan算法加缩点,缩点后求出度数为1的叶子节点个数,需要加边数为(leaf+1)/2 1 #include <iostream> 2 #include <cstdio> 3 #include <cstdlib> 4 #include <cmath> 5 #include <algorithm> 6 #include <cstring> 7

POJ 3352 边双联通

点击打开链接 题意:题目很长不说了,就是求加几条边后,任意删除一条边后,图还是联通的 思路:边双联通分量的定义就是删除一条边后图仍联通,这里推荐这篇点这里写的很详细,而这题就是推荐文章中的构造双联通图中桥的方法,那么我们直接引用,证明看那篇文章把,对于一个联通图,我们求出所有桥,求桥的方法与割点类似,都是求出low和dfs数组完成,我的代码中是L和E数组,将桥删除后的图肯定是多个联通块,而每个块肯定是一个双联通子图,这由桥的定义可以看出来,然后将每一块缩成一个点,连起来后找到度为1的点的个数+1

poj 3177 Redundant Paths (双联通)

/******************************************************* 题目:Redundant Paths (poj 2177) 链接:http://poj.org/problem?id=3177 算法:双联通+缩点 思路:先找出所有双联通分量,把这些分量缩成一个点 再找出所有度为一的点,用这些点数加一除2就可以了 ********************************************************/ #include<cs

POJ 1515 双联通分量

点击打开链接 题意:给一个联通的无向图,然后问你将其中的边变为有向的,加边使其变成有向的联通图 思路:若无向图有双联通分量,那么这个分量里的元素可以变成有向图的强联通,这应该很好看出来,然后需要加的边是什么呢,就是这个图上的桥呗,是桥的话变成有向的就要加一条边,然后剩下的无向图的双联通分量可以用dfs搜一下,边搜边输出就可以了,将桥记录下来遇到桥的时候特殊处理一下,然后双联通分量里的边每一条只能走一次,将走得边和反向边标记一下就行了  PS:vector写这样反向边的真是麻烦 #include

POJ 3177 Redundant Paths 无向图边双联通基础题

题意: 给一个无向图,保证任意两个点之间有两条完全不相同的路径 求至少加多少边才能实现 题解: 得先学会一波tarjan无向图 桥的定义是:删除这条边之后该图不联通 一条无向边(u,v)是桥,当且仅当(u,v)为树枝边,且满足 DFN(u)<Low(v).(因为 v 想要到达 u 的父亲必须经过(u,v)这条边,所以删去这条边,图不连通) 先用Tarjan无向图缩边双联通分量,这样原图就构成了一颗树, 对于树的叶子节点来说,显然他们需要连边,可以证明的是,我们连至多(叶子节点个数+1)/2的边就

poj3694 network(边双联通分量+lca+并查集)

题    目    传    送    们    在    这 题目大意 有一个由n个点和m条边组成的无向联通图. 现在有Q个操作,每次操作可以在点x,y之间连一条边. 问你每次操作后有多少个多少个桥(即删掉后就会使图不联通的边). 解题思路 根据边双联通的定义,我们知道将边双联通分量缩点后的图,其中的边即为桥. 我们将这个图缩点,就变成了一棵树. 而每次在两个不同的边双联通分量x,y之间加边后,就出现了一个包含x,y的环,其中原先这颗树上x,y的树上最短路径就不在是边. 所以对于每个x,y,我

poj-3177(并查集+双联通分量+Tarjan算法)

题目链接:传送门 思路: 题目要将使每一对草场之间都有至少两条相互分离的路径,所以转化为(一个有桥的连通图至少加几条边才能变为双联通图?) 先将桥删除,然后原图变为多个连通块,每一个连通块就是一个边双联通分量,将双联通子图收缩为一个顶点,再把桥边加回来,边连通度为1, 顺便统计度为1的节点的个数,即叶节点的个数即为cnt,所以至少在树上添加(cnt+1)/2条边. #include<iostream> #include<cstdio> #include<cstring>