codeforces 587C:(LCA倍增+维护最小值)

一开始直接无脑tarjan,回溯只能一层层往上走,太慢了,加了各种优化还是TLE

后来了解到LCA倍增法(在线)。复杂度其实相比LCA转RMQ以及tarjan是要稍差一些,但是其中能同步维护的只有LCA倍增,很神奇的算法

#include"cstdio"
#include"queue"
#include"cmath"
#include"stack"
#include"iostream"
#include"algorithm"
#include"cstring"
#include"queue"
#include"map"
#include"vector"
#define ll long long

using namespace std;
const int MAXN = 1e5+50;
const int MAXE = 200500;
const int INF = 0x3f3f3f;

struct node{
    int e,next;
    node(){}
    node(int a,int b):e(a),next(b){}
}edge[MAXE];

int n,m,q,tot;
int vis[MAXN],first[MAXN],deep[MAXN];
int p[MAXN][21];                    ///记录i往上跳2^j下到达的结点
vector<int> G[MAXN][21],ans,num[MAXN];///G[i][j]记录i往上2^j个结点的10个最小值(不包括i结点,为的是更新方便)

void init(){
    tot=0;
    memset(vis,0,sizeof(vis));
    memset(first,-1,sizeof(first));
    memset(p,-1,sizeof(p));
}

void addedge(int u,int v){
    edge[tot]=node(v,first[u]);
    first[u]=tot++;
    edge[tot]=node(u,first[v]);
    first[v]=tot++;
}

void update(vector<int> a,vector<int> b,vector<int> &c){    ///合并
    int sa=a.size();
    int sb=b.size();
    int i=0,j=0;
    int va,vb;
    while(i+j<10&&(i<sa||j<sb)){
        if(i<sa) va=a[i];
        else va=INF;
        if(j<sb) vb=b[j];
        else vb=INF;
        if(va<vb){
            c.push_back(va);
            i++;
        }
        else{
            c.push_back(vb);
            j++;
        }
    }
}

void Merge(vector<int> &a,vector<int> b){   ///合并
    int sb=b.size();
    for(int i=0;i<sb;i++) a.push_back(b[i]);
}

void init_p(){
    for(int j=1;(1<<j)<=n;j++)
    for(int i=1;i<=n;i++) if(p[i][j-1]!=-1) {
        p[i][j]=p[p[i][j-1]][j-1];                  ///i往上跳2^j相当于i往上跳2次2^(j-1)
        update(G[i][j-1],G[p[i][j-1]][j-1],G[i][j]);
    }
}

void dfs(int u,int dep){
    vis[u]=1;
    deep[u]=dep;
    for(int i=first[u];i!=-1;i=edge[i].next){
        int v=edge[i].e;
        if(vis[v]) continue;
        p[v][0]=u;          ///初始化,2^0=1,相当于父亲节点
        G[v][0]=num[u];     ///初始化
        dfs(v,dep+1);
    }
}

void get_lca(int u,int v,int k){
    if(deep[u]<deep[v]) swap(u,v);  ///保证deep[u]>deep[v]便于操作

    int a=u,b=v;        ///先不合并u,v内的元素,以免重复,故先保存下来
    ans.clear();

    int dif=deep[u]-deep[v];
    for(int i=0;i<=20;i++) if(dif&(1<<i)){
        Merge(ans,G[u][i]);
        u=p[u][i];
    }
    if(u!=v){
        for(int i=20;i>=0;i--) if(p[u][i]!=-1&&p[u][i]!=p[v][i]){
            Merge(ans,G[v][i]);
            Merge(ans,G[u][i]);
            v=p[v][i];
            u=p[u][i];
        }
        Merge(ans,num[a]);          ///这种情况是u,v分属lca的两个子树
        Merge(ans,num[b]);
        Merge(ans,num[p[u][0]]);
    }
    else Merge(ans,num[a]);         ///这种情况下v就是LCA,故只合并u
    sort(ans.begin(),ans.end());
    int t=min(k,(int)ans.size());
    cout<<t<<‘ ‘;
    for(int i=0;i<t;i++) cout<<ans[i]<<‘ ‘;
    cout<<endl;
}

int main(){
    //freopen("in.txt","r",stdin);
    scanf("%d%d%d",&n,&m,&q);
    init();

    for(int i=1;i<n;i++){
        int u,v;scanf("%d%d",&u,&v);
        addedge(u,v);
    }
    for(int i=1;i<=m;i++){
        int t;scanf("%d",&t);
        if(num[t].size()<10) num[t].push_back(i);
    }
    dfs(1,1);
    init_p();
    for(int i=0;i<q;i++){
        int u,v,k;
        scanf("%d%d%d",&u,&v,&k);
        get_lca(u,v,k);
    }
    return 0;
}

时间: 2024-10-23 15:26:01

codeforces 587C:(LCA倍增+维护最小值)的相关文章

洛谷.4180.[模板]次小生成树Tree(Kruskal LCA 倍增)

构建完MST后,枚举非树边(u,v,w),在树上u->v的路径中找一条权值最大的边(权为maxn),替换掉它这样在 w=maxn 时显然不能满足严格次小.但是这个w可以替换掉树上严格小于maxn的次大边用倍增维护MST上路径的最大值.次大值,每条非树边的查询复杂度就为O(logn) ps:1.倍增更新次大值时,未必是从最大值转移,要先赋值较大的次大值,再与较小的那个最大值比较.2.maxn!=w时,是可以从maxn更新的(不能更新就是上面情况啊)倍增处理部分我还是在dfs里写吧 md改了一晚上

次小生成树(LCA倍增)

算法: 求出MST之后枚举每条在MST之外的边 连上之后会出现环 找到环中除加上的边之外权值最大的边 删除该边之后得到一颗新树 做法: 利用LCA倍增地维护最小生成树上两点之间的最大边权 每次枚举在MST之外的边 有两种情况 ①.两个端点在一条链上 ②.两个端点不在一条链上 第一种情况就直接得到答案 第二种情况的话分两步处理取MAX 复杂度mlogn 严格 bzoj1977 严格的话不仅要处理出maxe[i][j]还要处理出次大的maxe2[i][j] 因为当两点之间的边权最大值等于加上的边权的

LCA(倍增在线算法) codevs 2370 小机房的树

codevs 2370 小机房的树 时间限制: 1 s 空间限制: 256000 KB 题目等级 : 钻石 Diamond 题目描述 Description 小机房有棵焕狗种的树,树上有N个节点,节点标号为0到N-1,有两只虫子名叫飘狗和大吉狗,分居在两个不同的节点上.有一天,他们想爬到一个节点上去搞基,但是作为两只虫子,他们不想花费太多精力.已知从某个节点爬到其父亲节点要花费 c 的能量(从父亲节点爬到此节点也相同),他们想找出一条花费精力最短的路,以使得搞基的时候精力旺盛,他们找到你要你设计

UVa 11354 Bond 最小生成树+LCA倍增

题目来源:UVa 11354 Bond 题意:n个点m条边的图 q次询问 找到一条从s到t的一条边 使所有边的最大危险系数最小 思路:使最大的危险系数尽量小 答案是最小生成树上的边 然后用LCA倍增法记录s和t到他们最近公共祖先的最大值 #include <cstdio> #include <cstring> #include <algorithm> using namespace std; const int maxn = 50010; const int INF =

HDU 4362 Dragon Ball(维护最小值DP优化)

 题意: 在连续的 n 秒中,每秒会出现 m 个龙珠,出现之后会立即消失,知道了第一秒所在的位置,每从一个位置移动到另一个位置的时候,消耗的价值为 abs(i-j), 知道了次出现的龙珠的价值,问 n 秒之后得到的最大价值是多少. 思路:这道题朴素的做法时间复杂度为O(n*n*m)勉强可以水过去,正解应该是用单调队列的思路维护最小值优化. 由状态转移方程dp[i][j] = min{ dp[i-1][k] + abs(pos[i-1][k]-pos[i][j]) } + cost[i][j]

URAL 1752. Tree 2 树的直径+LCA倍增

题目来源:URAL 1752. Tree 2 题意:求一个点v与它距离为d的任意一个点 没有输出0 思路:开始想倍增法 但是倍增法只能往他的祖先去 后来百度发现了树的直径 想了想 发现可以建2棵树 每一棵树的根是树的直径的2个端点 这样保证了每个点和他距离最远的点就是其中一个根 因为一个点到树的直径的端点的距离是最远的 最后就是LCA倍增了 #include <cstdio> #include <cstring> #include <algorithm> #includ

HDU 4547 LCA倍增算法

CD操作 Time Limit: 10000/5000 MS (Java/Others)    Memory Limit: 65535/32768 K (Java/Others)Total Submission(s): 1111    Accepted Submission(s): 297 Problem Description 在Windows下我们可以通过cmd运行DOS的部分功能,其中CD是一条很有意思的命令,通过CD操作,我们可以改变当前目录. 这里我们简化一下问题,假设只有一个根目录,

LCA倍增算法

LCA 算法是一个技巧性很强的算法. 十分感谢月老提供的模板. 这里我实现LCA是通过倍增,其实就是二进制优化. 任何一个数都可以有2的阶数实现 例如16可以由1 2 4 8组合得到 5可以由1 2 4 组合得到 便于读者理解 我放一道例题吧 Problem F: 挑战迷宫 Description 小翔和小明正在挑战一个神奇的迷宫.迷宫由n个房间组成,每个房间的编号为1~n,其中1号房间是他们俩初始位置, 所有房间一共由n-1条路连接,使得房间两两之间能够相互达到(构成一棵树),每条路的长度为W

codeforces 519E A and B and Lecture Rooms LCA倍增

Time Limit:2000MS     Memory Limit:262144KB     64bit IO Format:%I64d & %I64u Submit Status Practice CodeForces 519E Description A and B are preparing themselves for programming contests. The University where A and B study is a set of rooms connected