2015蓝桥杯C++A组——灾后重建

灾后重建题目链接

问题描述

【题目描述】
在这里插入图片描述
【输入】
在这里插入图片描述
【输出】
在这里插入图片描述
【样例输入】

7 10 4
1 3 10
2 6 9
4 1 5
3 7 4
3 6 9
1 5 8
2 7 4
3 2 10
1 7 6
7 6 9
1 7 1 0
1 7 3 1
2 5 1 0
3 7 2 1

【样例输出】

9
6
8
8

题目解析

最小生成树简单算法

对于每一次问询,关注若干点后,我们需要按照最小生成树的方法生成n-1条边,此时关注点全部连通,然后求出其中的最大边就是答案。

倍增法优化最小生成树

新思路

  1. 首先生成最小生成树 M S T MST MST
  2. 对每一次问询都要将关注点放入 M S T MST MST
  3. 维护 m a x c o s t max{cost} maxcost
    在维护 m a x c o s t max{cost} maxcost用到倍增法求最近公共祖先lca,倍增法就是要用额外的数据来存放每一个点。 f f [ i ] [ j ] ff[i][j] ff[i][j]表示点i向上移动 2 i 2^i 2i次到达节点的标号。

倍增法LCA

  1. f f [ ] [ ] ff[][] ff[][]数组进行预处理,用到了 d f s dfs dfs算法;
  2. l c a lca lca,将 a a a b b b调到同一个高度;
  3. 在2的过程中维护 m a x max max s u m sum sum a v g avg avg m i n min min

C++代码

最小生成树简单算法

#include<bits/stdc++.h>
using namespace std;
int N,M,Q;
const int MaxM = 2*10^5;
const int MaxN = 50001;
struct Edge //边的抽象
{
    int from,to,cost; //起点终点代价
    Edge(int from,int to,int cost)
    {
        this->from = from;
        this->to = to;
        this->cost = cost;
    }
};
bool cmp(Edge *e1,Edge *e2)
{
    return e1->cost<e2->cost;
}
Edge *edges[MaxM];
//并查集
struct UFNode
{
    UFNode *parent;
    UFNode():parent(NULL){}
};
UFNode *find(UFNode *p)
{
    if(p->parent==NULL) return p;
    set<UFNode*> path;
    while(p->parent!=NULL)
    {
        path.insert(p);
        p = p->parent;
    }
    set<UFNode *>::iterator iter = path.begin();
    while(iter!=path.end())
    {
        (*iter)->parent=p;
        iter++; //指针后移
    }
    return p;
}
void merge(UFNode *p1,UFNode *p2) //并查集合并
{
    find(p2)->parent = find(p1);
}
UFNode ufnodes[MaxN]; //并查集的节点,一开始各自独立
void easy(int l,int r,int mod,int c)
{
    for(int j=0;j<=N;j++) ufnodes[j].parent = NULL; //重新初始化NULL
    //逐步加入边到最小生成树中
    for(int i=0;i<M;i++)
    {
        Edge *pEdge = edges[i];
        int from = pEdge->from;
        int to = pEdge->to;
        int cost = pEdge->cost;
        if(find(&ufnodes[from])==find(&ufnodes[to])) continue; //如果这两个点已经在一棵树上就不采纳
        else merge(&ufnodes[from],&ufnodes[to]); //并查集合并
        UFNode * parent = NULL;
        bool isOk = true;
        for(int i=l;i<=r;i++)
        {
            if(i%mod==c) //i值关注点的编号
            {
                if(parent==NULL) parent = find(&ufnodes[i]); //第一个关注点的老大
                else if(parent!=find(&ufnodes[i])) //没有联通
                {
                    isOk = false;
                    break;
                }
            }
        }
        if(isOk) //关注点都联通了
        {
            printf("%d\n",cost);
            break;
        }
    }
}
int main()
{
    scanf("%d %d %d",&N,&M,&Q);
    for(int i=0;i<M;i++)
    {
        int a,b,c;
        scanf("%d %d %d",&a,&b,&c);
        Edge *e = new Edge(a,b,c);
        edges[i] = e;
    }
    sort(edges,edges+M,cmp); //排序
    for(int i=0;i<Q;i++)
    {
        int l,r,mod,c;
        scanf("%d %d %d %d",&l,&r,&mod,&c);
        easy(l,r,mod,c);
    }
    return 0;
}

倍增法优化最小生成树代码

#include<bits/stdc++.h>
using namespace std;
int N,M,Q;
const int MaxM = 2*10^5;
const int MaxN = 50001;
int ff[MaxN][17]; //ff[i][j]表示标号为i的节点往根节点的方向移动2^i次达到的节点编号
int mm[MaxN][17]; //mm[i][j]指的是标号为i的节点往根节点的方向移动2^i次过程中的最大权
int depth[MaxN]; //记录每个点在mst中的深度
int vis[MaxN]; //记录耨个是否被访问过
struct Edge //边的抽象
{
    int from,to,cost; //起点终点代价
    Edge(int from,int to,int cost)
    {
        this->from = from;
        this->to = to;
        this->cost = cost;
    }
};
bool cmp(Edge *e1,Edge *e2)
{
    return e1->cost<e2->cost;
}
Edge *edges[MaxM];
//并查集
struct UFNode
{
    UFNode *parent;
    UFNode():parent(NULL){}
};
UFNode *find(UFNode *p)
{
    if(p->parent==NULL) return p;
    set<UFNode*> path;
    while(p->parent!=NULL)
    {
        path.insert(p);
        p = p->parent;
    }
    set<UFNode *>::iterator iter = path.begin();
    while(iter!=path.end())
    {
        (*iter)->parent=p;
        iter++; //指针后移
    }
    return p;
}
void merge(UFNode *p1,UFNode *p2) //并查集合并
{
    find(p2)->parent = find(p1);
}
UFNode ufnodes[MaxN]; //并查集的节点,一开始各自独立
int maxUsingLca(int a,int b) //倍增法求lca顺便求max权重
{
    int ans = -1;
    if(depth[a]<depth[b]) //将a的深度调到更深
    {
        int t=a;
        a=b;
        b=t;
    }
    int k=depth[a]-depth[b]; //将a调到和b同一高度
    for(int i=0;(1<<i)<=k;i++)
    {
        if((1<<i)&k)
        {
            ans = max(ans,mm[a][i]);
            a = ff[a][i];
        }
    }
    //至此a和b已经在同一层上
    for(int j=16;j>=0;j--)
    {
        if(ff[a][j]==ff[b][j]) continue;
        else
        {
            ans = max(ans,mm[a][j]);
            ans = max(ans,mm[b][j]);
            a = ff[a][j];
            b = ff[b][j];
            break;
        }
    }
    ans = max(ans,mm[a][0]);
    ans = max(ans,mm[b][0]); //再往上走一步就得到了lca
    return ans;
}
void mid(int l,int r,int mod,int c)
{
    int ans = -1;
    int left = l-l%mod+c;
    if(left<l) left += mod;
    for(;left+mod<=r;left+=mod)
    {
        ans = max(ans,maxUsingLca(left,left+mod));
    }
    printf("%d\n",ans);
}
//最小树的生成及表示
vector<Edge *> mst[MaxN];
void buildMST()
{
    int cnt = 0; //已加入边的数量
    //逐步加入边到最小生成树中
    for(int i=0;i<M;i++)
    {
        Edge *pEdge = edges[i];
        int from = pEdge->from;
        int to = pEdge->to;
        int cost = pEdge->cost;
        if(find(&ufnodes[from])==find(&ufnodes[to])) continue; //如果这两个点已经在一棵树上就不采纳
        else
        {
            merge(&ufnodes[from],&ufnodes[to]); //并查集合并
            cnt++;
            mst[from].push_back(pEdge);
            Edge *other = new Edge(to,from,cost);
            mst[to].push_back(other);
            if(cnt==N-1) //构建完成
            {
                break;
            }
        }
    }
}
void dfs(int start,int parent,int d) //start表示开始点编号,parent表示父结点编号,d表示这个点的深度
{
    depth[start] = d+1;
    vis[start] = 1;
    //向上走
    for(int i=1;i<17;i++)
    {
        ff[start][i] = ff[ff[start][i-1]][i-1];
        mm[start][i] = max(mm[start][i-1],mm[ff[start][i-1]][i-1]);
    }
    //向下递归找到所有儿子
    for(int i=0;i<mst[start].size();i++)
    {
        Edge *child = mst[start][i]; //儿子
        if(vis[child->to]) continue;
        ff[child->to][0] = start;
        mm[child->to][0] = child->cost;
        dfs(child->to,start,d+1);
    }
}
void preLca()
{
    ff[1][0] = 1; //定义1号节点为树的根节点
    mm[1][0] = 0; //定义1号节点为根节点
    dfs(1,1,0);
}
int main()
{
    scanf("%d %d %d",&N,&M,&Q);
    for(int i=0;i<M;i++)
    {
        int a,b,c;
        scanf("%d %d %d",&a,&b,&c);
        Edge *e = new Edge(a,b,c);
        edges[i] = e;
    }
    sort(edges,edges+M,cmp); //排序
    buildMST(); //生成最小树
    preLca(); //为倍增法做预处理
    for(int i=0;i<Q;i++)
    {
        int l,r,mod,c;
        scanf("%d %d %d %d",&l,&r,&mod,&c);
        mid(l,r,mod,c);
    }
    return 0;
}
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

芷汀若静

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值