【题解】 NC5205E 水灾

水灾

https://ac.nowcoder.com/acm/contest/5205/E

solution

首先可以发现,这张图其实可以转化为一棵最大生成树。

我们按权值从大到小加入边,如果新加入的边所连接的两点,已经可以通过其他权值更大的边相连,后面的询问中只删除这条边起不到任何作用。

所以我们考虑建出 重构树

具体建立方法:对于在生成树中的一条边,新建一个节点,并且从所在的集合分别连边。将的点权设为的边权。并将所在的集合改为。最终重构树的根节点为最后新建的节点。

重构树中,每个生成树中的节点在重构树中全都是叶子节点。生成树中两个节点之间的路径最小值为重构树中这两个点的点权。

利用这条性质,我们考虑回答询问。对于每个询问,我们如果暴力处理,可以删除这个点任意两个点路径上的最小权值,也就是在重构树上这两个点的。最后答案就是这些中权值最大值。

因为重构树上一个节点的权值小于它子树的权值。所以我们想要深度比较大的那些就行了。(感性理解一下吧,我也不知道该咋说了qwq)。所以按照dfs序排个序。然后每次查询相邻的两个点的即可。

下面来说为什么直接建立生成树是错的!

先给出一组hack数据(并不一定能hack到所有这种程序,因为排序结果和具体建数方式有关)

7 6 1
1 2 5
1 5 5
2 3 5
2 4 1
5 6 5
5 7 5
3 3 4 6

也就是下面这张图。

我们要求不能连通,按照序排序后就是的顺序。路径上的最小边是路径上的最小边是。这样我们两次计算删掉的都是这条边,而此时仍是可以互达的。然后就了。

code

/*
* @Author: wxyww
* @Date:   2020-04-24 21:17:32
* @Last Modified time: 2020-04-25 19:51:50
*/
#include<cstdio>
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<queue>
#include<vector>
#include<ctime>
using namespace std;
typedef long long ll;
const int N = 1000010,logN = 20;
ll read() {
    ll x = 0,f = 1;char c = getchar();
    while(c < '0' || c > '9') {
        if(c == '-') f = -1; c = getchar();
    }
    while(c >= '0' && c <= '9') {
        x = x * 10 + c - '0'; c = getchar();
    }
    return x * f;
}
struct node {
    int u,v,w;
}a[N];
bool cmp(const node &A,const node &B) {
    return A.w > B.w;
}

vector<int>e[N];

int S[N],w[N],id[N],fa[N];
int find(int x) {
    return fa[x] == x ? x : fa[x] = find(fa[x]);
}
void uni(int x,int y,int k) {
    x = x,y = y;
    if(rand() & 1) swap(x,y);
    fa[x] = y;

    S[y] = k;
}
int tot,cnt;
int dep[N],lca[N][logN + 2];
void dfs(int u) {
    id[u] = ++cnt;

    for(int i = 1;i <= logN; ++i) {
        lca[u][i] = lca[lca[u][i - 1]][i - 1];
    }

    for(vector<int>::iterator it = e[u].begin();it != e[u].end();++it) {
        lca[*it][0] = u;
        dep[*it] = dep[u] + 1;
        dfs(*it);
    }
}

int LCA(int x,int y) {
    if(dep[x] < dep[y]) swap(x,y);
    for(int i = logN;i >= 0;--i) {
        if(dep[lca[x][i]] >= dep[y]) {
            x = lca[x][i];
        }
    }
    for(int i = logN;i >= 0;--i) {
        if(lca[x][i] != lca[y][i])
            x = lca[x][i],y = lca[y][i];
    }
    if(x != y) x = lca[x][0];
    return x;
}
int tmp[N];
bool cmp2(int x,int y) {
    return id[x] < id[y];
}
int main() {
    int n = read(),m = read(),Q = read();
    for(int i = 1;i <= m;++i) {
        int u = read(),v = read(),w = read();
        a[i].u = u;a[i].v = v;a[i].w = w;
    }

    sort(a + 1,a + m + 1,cmp);
    for(int i = 1;i <= n;++i) fa[i] = i,S[i] = i;
    tot = n;
    for(int i = 1;i <= m;++i) {
        int u = a[i].u,v = a[i].v;
        u = find(u),v = find(v);
        if(u != v) {
            ++tot;
            // printf("%d %d\n%d %d\n",tot,S[u],tot,S[v]);
            e[tot].push_back(S[u]);
            e[tot].push_back(S[v]);
            w[tot] = a[i].w;
            uni(u,v,tot);
        }
    }

    dfs(tot);
    int lst = 0;
    while(Q--) {
        int k = read();
        for(int i = 1;i <= k;++i) tmp[i] = read() ^ lst;
        sort(tmp + 1,tmp + k + 1,cmp2);
        lst = 0;
        for(int i = 2;i <= k;++i) {
            printf("%d %d\n",tmp[i - 1],tmp[i]);
            lst = max(lst,w[LCA(tmp[i - 1],tmp[i])]);
        }
        printf("%d\n",lst);
    }
    return 0;
}

/*

7 6 1
1 2 5
1 5 5
2 3 5
2 4 1
5 6 5
5 7 5
3 3 4 6
*/
全部评论

相关推荐

秋招进行到现在终于能写总结了。完全没想到战线会拉这么长,过程会如此狼狈,不过更应该怪自己太菜了。好在所有的运气都用在了最后,也是有个去处。背景:双2本硕科班,无竞赛,本科一段研究所实习,硕士一段大厂暑期实习但无转正。技术栈是C++&nbsp;&amp;&nbsp;Golang,实习是客户端音视频(而且是鸿蒙端开发),简历两个C++项目一个Golang项目。主要投递岗位:后端,cpp软开,游戏服务端,测开,以及一些不拘泥于Java的岗位。从8月起总共投递123家公司,笔试数不清了,约面大约30家。offer/oc/意向:友塔游戏(第一个offer,面试体验很好,就是给钱好少南瑞继保(计算机科班点击就送(限男生),不...
乡土丁真真:佬很厉害,羡慕~虽然我还没有到校招的时候,也想讲一下自己的看法:我觉得不是CPP的问题,佬的背书双2,技术栈加了GO,有两段实习。投了123,面了30.拿到11个offer。这个数据已经很耀眼了。这不也是CPP带来的吗?当然也不止是CPP。至少来说在这个方向努力过的也会有好的结果和选择。同等学历和项目选java就会有更好的吗?我个人持疑问态度。当然CPP在方向选择上确实让人头大,但是我觉得能上岸,至于最后做什么方向,在我看来并不重要。至于CPP特殊,有岗位方向的随机性,java不是不挑方向,只是没得选而已。也希望自己以后校招的时候能offer满满
点赞 评论 收藏
分享
评论
1
收藏
分享
牛客网
牛客企业服务