【计蒜客习题】蒜头君运送宝藏



!!!原来LCA的题可以出的这么难,完了这还属于水题?!

先来解释一下题意,有N个城市,在这N城市之间有M条边(不一定每个城市都有边)。我们的任务是找出给定两个点之间路径上的最小边权,使得这个最小边权尽量大。一开始我很纳闷,这和LCA有什么关系呢,怎么和最大流有点像。冥思苦想(看了别人的想法)之后,哦,原来是最大生成树+LCA。。。因为是最大生成树,可以使得任意两点间路径上的最大边权最大(再连上较小的会成环)。这样问题就变成了找树上两点间路径上的最小边权,显然可以利用LCA,这是他十分经典的应用。设minn[i][j]表示从结点i向上蹦2^j次,中间路径上的最小边权,则minn[i][0]=w<father[i],i>(从其父亲到结点i的边权),minn[i][j]=min(minn[i][j-1],minn[fa[i][j-1]][j-1])(j!=0),相当于将路径二分。

这道题最纠结的在于一开始建树的过程,是用Kruskal没有疑问,但是真的是建无向图吗?树根怎么确定?修改了好久才弄对,代码及其。。。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=1e4+5,maxm=5e4+5;
int n,m,head[maxn],eid;
struct edge {
    int u,v,w;
    bool operator < (const edge& rhs) const {
        return w>rhs.w;
    }
} E[maxm];
struct edge2 {
    int v,w,next;
} e[2*maxn];
void insert(int u,int v,int w) {
    e[eid].v=v,e[eid].w=w;
    e[eid].next=head[u];
    head[u]=eid++;
}
int dj_fa[maxn];
int dj_find(int i) {
    if(i==dj_fa[i]) return i;
    return dj_fa[i]=dj_find(dj_fa[i]);
}
void dj_merge(int a,int b) {
    a=dj_find(a),b=dj_find(b);
    if(a!=b) dj_fa[b]=a;
}
int vis[maxn],d[maxn],fa[maxn][20],minn[maxn][20];
void dfs(int u) {
    for(int p=head[u];p+1;p=e[p].next) {
        int v=e[p].v;
        if(d[v]!=-1) continue;
        d[v]=d[u]+1;
        fa[v][0]=u;
        minn[v][0]=e[p].w;
        dfs(v);
    }
}
int lca(int x,int y) {
    int i,j,mind=0x3f3f3f3f;
    if(d[x]<d[y]) swap(x,y);
    for(i=0;(1<<i)<=d[x];++i);--i;
    for(j=i;j>=0;--j) if(d[x]-(1<<j)>=d[y]) {
        mind=min(mind,minn[x][j]);
        x=fa[x][j];
    }
    if(x==y) return mind;
    for(j=i;j>=0;--j) if(fa[x][j]!=fa[y][j]) {
        mind=min(mind,min(minn[x][j],minn[y][j]));
        x=fa[x][j],y=fa[y][j];
    }
    mind=min(mind,min(minn[x][0],minn[y][0]));
    return mind;
}
int main() {
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;++i) scanf("%d%d%d",&E[i].u,&E[i].v,&E[i].w);
    sort(E+1,E+m+1);
    for(int i=1;i<=n;++i) dj_fa[i]=i;
    memset(head,-1,sizeof(head));
    for(int i=1;i<=m;++i) {
        int u=E[i].u,v=E[i].v,w=E[i].w;
        if(dj_find(u)!=dj_find(v)) {
            dj_merge(u,v);
            insert(u,v,w);insert(v,u,w);
            vis[u]=vis[v]=1;
        }
    }
    int root;
    for(int i=1;i<=n;++i) if(dj_fa[i]==i&&vis[i]) root=i;
    memset(d,-1,sizeof(d));d[root]=0;
    dfs(root);
    for(int j=1;(1<<j)<=n;++j)
        for(int i=1;i<=n;++i) {
            fa[i][j]=fa[fa[i][j-1]][j-1];
            minn[i][j]=min(minn[i][j-1],minn[fa[i][j-1]][j-1]);
        }
    int q,a,b,first=1;
    scanf("%d",&q);
    for(int i=1;i<=q;++i) {
        scanf("%d%d",&a,&b);
        if(first) first=0;
        else putchar(‘\n‘);
        if(dj_find(a)!=dj_find(b)) printf("-1");
        else printf("%d",lca(a,b));
    }
    return 0;
}

AC代码

之所以代码里面那样做可以,是因为最大生成树所不包含的点仅仅是那些孤零零的点,而并查集本质就是一棵树(树的结点只有一个父亲,必须满足这个条件),这样并查集里父亲是自身的就是树根了(不唯一,不同的方法可以找到不同的树根)。两点之间无法到达就是指其中一个是孤零零的点。

原文地址:https://www.cnblogs.com/Mr94Kevin/p/9650467.html

时间: 2024-10-02 22:50:36

【计蒜客习题】蒜头君运送宝藏的相关文章

【计蒜客习题】圣诞树

问题描述 圣诞节快到了,蒜头君准备做一棵大圣诞树.这棵树被表示成一组被编号的结点和一些边的集合,树的结点从 1 到 n 编号,树的根永远是 1.每个结点都有一个自身特有的数值,称为它的权重,各个结点的权重可能不同.对于一棵做完的树来说,每条边都有一个价值 ve,若设这条边 e 连接结点 i 和结点 j,且 i 为 j的父结点(根是最老的祖先),则该边的价值ve=sj*we,sj表示结点 j 的所有子孙及它自己的权重之和,we表示边 e 的权值.现在蒜头君想造一棵树,他有 m 条边可以选择,使得树

【计蒜客习题】 取石子游戏(gcd)

问题描述 蒜头君和花椰妹在玩一个游戏,他们在地上将 n 颗石子排成一排,编号为 1 到 n.开始时,蒜头君随机取出了 2 颗石子扔掉,假设蒜头君取出的 2 颗石子的编号为 a, b.游戏规则如下,蒜头君和花椰妹 2 人轮流取石子,每次取石子,假设某人取出的石子编号为 i,那么必须要找到一对 j, k 满足 i=j−k 或者 i=j+k ,并且编号为 j,k 的石子已经被取出了,如果谁先不能取石子了,则视为输了.蒜头君比较绅士,让花椰妹先手. 输入格式 第一行输入一个整数 t(1≤t≤500),表

【计蒜客习题】 蒜头君的猜想(埃氏筛)

解题思路: 从1到n将所有数筛一遍,枚举加数,如果这两个加数都是质数,那么方案数加1. AC代码: 1 #include<cstdio> 2 3 using namespace std; 4 5 int n,ans; 6 bool vis[8000001]; 7 8 int main() 9 { 10 scanf("%d",&n); 11 for(int i = 2;i * i <= n; i++) 12 if(!vis[i]) { 13 for(int j

计蒜客普及组模拟赛

今天没事闲的看到计蒜客有个普及组模拟赛,就当练了练手去打了,成绩低的可怜...400分崩成280分AK梦想化作泡影 第一题 同学的爱好 链接:https://nanti.jisuanke.com/t/17291 小学应用题难度?大概画个图就能懂,把每个部分都标上号,算出a,b,c,d,e,f的部分,进行运算就行了. 不多解释了,直接上代码 #include<iostream> #include<cstdio> #include<algorithm> #include&l

计蒜客 作弊揭发者(string的应用)

鉴于我市拥堵的交通状况,市政交管部门经过听证决定在道路两侧安置自动停车收费系统.当车辆驶入车位,系统会通过配有的摄像头拍摄车辆画面,通过识别车牌上的数字.字母序列识别车牌,通过连接车管所车辆信息数据库确认车辆,进行扣费. 斗智斗勇的好戏一般从此处展开… 一些车主通过在停车时遮挡车牌上的一个或多个数字.字母序列,来阻碍识别系统的识别工作,以此逃避停车费用的缴纳. 车主这简直是用轻轻的一挡搞出来一个世界难题有木有?!管理是一方面,技术解决才是王道啊. 这么难的项目不得不交给计蒜客实验室了.D 神负责

计蒜客 无脑博士和他的试管们

无脑博士有三个容量分别是A,B,C升的试管,A,B,C分别是三个从1到20的整数,最初,A和B试管都是空的,而C试管是装满硫酸铜溶液的.有时,无脑博士把硫酸铜溶液从一个试管倒到另一个试管中,直到被灌试管装满或原试管空了.当然每一次灌注都是完全的.由于无脑博士天天这么折腾,早已熟练,溶液在倒的过程中不会有丢失. 写一个程序去帮助无脑博士找出当A是个是空的时候,C试管中硫酸铜溶液所剩量的所有可能性. 输入包括一行,为空格分隔开的三个数,分别为整数A,B和C. 输出包括一行,升序地列出当A试管是空的时

简单斐波那契——计蒜客(4)

题目来自“计蒜客”第4题. 解算法题之前,务必先写出与之对应的数学表达式,用于描述算法. 数学描述如图: 根据“数学描述“,写出代码如下: #include <stdio.h> int main() { int N =0 ; scanf("%d", &N); int i, fn1 = 1, fn2 = 0, fn; switch(N) { case 0: printf("0"); break; case 1: printf("1&quo

计蒜客 删除字母&#39;c&#39;

删除字母'c' 右侧的程序实现的功能是从字符串s中删除所有的小写字母c,请改正程序错误的地方. 注意:main函数不可以改动,程序结构也不能修改. 很简单的哦,加油吧- 样例输入 abccabcn 样例输出 ababn 1 #include <stdio.h> 2 3 #define N 1024 4 5 void del(char *s) 6 { 7 int i, j; 8 for(i = j = 0; s[i] != '\0'; i++) 9 { 10 if(s[i] != 'c') 11

计蒜客 教科书般的亵渎

Description: 环境里有 nn 个怪物,他们的生命值用一个正整数表示.现在,你可以使用两种魔法,对怪物进行攻击.当怪物的生命值小于等于 00 时,他便被消灭了. 魔法箭,对摸个生物造成 kk 点伤害,对一个生物最多使用一次,但没有使用次数限制. 亵渎,对所有生物造成一点伤害,如果杀死了某个生物,则继续自动重新使用该法术.只能主动使用一次,且必须最后使用. 请问,最多能消灭多少个怪物?亵渎法术最多能释放几次? Input: 第一行两个整数 nn 和 kk ,表示怪物的数量和法术的伤害.第