【在线lca 模板】LCA 练习题2 : hdu 3078 + poj2763 我要记一辈子的s b错误

poj 2763 : 真的好傻逼的错误。
先讲题意:
先刷入 n q cur;
然后输入 n-1 条边,构成一棵树
然后q 行,每行一个操作: op 若op = 1,则输入 pos,w , 代表将上面的第pos条边的权值修改为w;
若op=0, 输入to,则代表将cur 移动到to这个位置,并输出最短的距离。

有人说是很标准的树链剖分, 但这题用lca 也能做一做。时间复杂度有点糟TUT
其实询问就是普通的lca 查询。
但是更新会有点麻烦。 其实并不复杂。
要修改第i 条边,则 tot =2*i 和2 * i+1。 修改完之后要怎么更新呢???
仔细想想,其实我们最朴素的想法就可以实现:我们直接往更深的地方更新dir即可。 因为dir 存的是到root的距离,然后我们把改变的边的终点找出来,往下更新即可,但是更新的时候呢,只能往下,也就是只能往rmq更大的节点更新。 !!!!!!!!!!错误来了,对于节点i 的深度我居然特么写成了 rmq[i],真的好蠢啊,rmq长度为2*MAXN,兄弟,rmq 对应的是F ,怎么能直接就写成rmq[i]啊,我真是醉了,完了自己debug了一下午+一晚上。 i的深度肯定得写成 rmq[first[i]]。

#include <cstdio>
#include <cmath>
#include <cstring>
#include <ctime>
#include <iostream>
#include <algorithm>
#include <set>
#include <vector>
#include <sstream>
#include <queue>
#include <typeinfo>
#include <fstream>
#include <map>
#include <stack>
typedef long long ll;
using namespace std;

const int MAXN=100010;
int rmq[2*MAXN]; //rmq数组, 记录每个节点在树中的深度
struct ST{
    int mm[2*MAXN];
    int dp[2*MAXN][20]; //dp 直接存下标
    void init(int n){
        mm[0]=-1;
        for(int i=1;i<=n;i++){
            mm[i]=((i&(i-1))==0)? mm[i-1]+1 :mm[i-1]; //???莫名其妙定义长度
            dp[i][0]=i; //初始化dp数组
        }
        for(int j=1;j<=mm[n];j++)   //可以看出来mm[n] 就是一个长度为n的序列j的最大值
            for(int i=1;i+(1<<j)-1 <=n ; i++){
                if(rmq[dp[i][j-1]] < rmq[dp[i+(1<<(j-1))][j-1] ]){
                    dp[i][j]=dp[i][j-1];
                }
                else
                    dp[i][j]=dp[i+(1<<(j-1))][j-1];
            }
    }
    int query(int a,int b){  //这个询问返回的是位置pos, F[pos]才是值
            if(a>b)
                swap(a,b);
            int k=mm[b-a+1];
            if(rmq[dp[a][k]] <= rmq[dp[b-(1<<k)+1][k]] ){
                return dp[a][k];
            }
            else
                return dp[b-(1<<k)+1][k];
    }
};
struct Edge{
    int to,next;
    int w; // 用w来记录权值:即到根部的距离。
};
Edge edge[MAXN*2];
int tot,head[MAXN];
int F[MAXN*2];   // 按顺序存储节点 ,下标从1开始,长度为2*n-1
int first[MAXN]; // i在F中第一次出现的位置
int cnt;
int fa[MAXN];  //  记录每个点的父亲节点,以此来找路径会特别方便
int w[MAXN];
int dir[MAXN];
ST st;

void addedge(int u,int v,int w){ //无向边自然加两次
    edge[tot].to=v;
    edge[tot].w=w;
    edge[tot].next=head[u];
    head[u]=tot++;
}
void dfs(int u,int pre,int dep){ //u起点,dep表示深度
     F[++cnt] = u;    // cnt就是这个节点的下标
     fa[u]=pre;
     rmq[cnt]=dep;    //rmq记录节点在树中的深度
//     printf("dfs:%d %d %d %d\n",u,dep,rmq[c],pre);
     first[u]=cnt;       //这个first 不会被更新掉,因为我们把continue了回去的路
     for(int i=head[u];i!=-1; i=edge[i].next){
         int v=edge[i].to;
         if(v==pre) continue;

       dir[v]=dir[u] + edge[i].w;
         dfs(v,u,dep+1);
         F[++cnt] = u;     //虽然continue回去的路,但是询问叶子节点还要返回:1-2-1 --....
         rmq[cnt] = dep;
     }
}
void LCA_init(int root ,int node_num){ //查询LCA前的初始化
    cnt=0;
    dfs(root,root,0);
    st.init(2*node_num-1); // 注意 ,这里dp的不是n,而是对F进行dp
}
int query_lca(int u,int v){   //查询lca(u,v)
    return F[st.query(first[u],first[v])];
}

bool flag[MAXN];
void work(int u,int pre,int cnt){
    dir[u]+=cnt;
    for(int i=head[u];i!=-1; i=edge[i].next){
             int v=edge[i].to;
             if(v==pre) continue;
             if(rmq[first[u] ]<rmq[first[v]])
                 work(v,u,cnt);
    }
}
void init(){
    tot=0;
    memset(head,-1,sizeof(head));
    memset(flag,0,sizeof(flag));
//  memset(rmq,0,sizeof(rmq));
}
int main(){
    //freopen("1.txt","r",stdin);
    int n,q,cur;
    while(~scanf("%d %d %d",&n,&q,&cur)){
        init();
        for(int i=1;i<n;i++){
            int a,b,c;
            scanf("%d %d %d",&a,&b,&c);
            addedge(a,b,c);  // 第i条边  为edge[(i-1)*2]  和edge[(i-1)*2+1]
            addedge(b,a,c);
            flag[b]=true;
        }
        int root;
                for(int i=1;i<=n;i++)
                    if(!flag[i]){ //某个根
                        root=i;
                        break;
                    }
//              mem()
        dir[root]=0;
//      printf("root=%d\n",root);
        LCA_init(root,n);

        while(q--){
            int op;
            scanf("%d",&op);
            if(op==0){
                int to;
                scanf("%d",&to);
                int end=query_lca(cur,to);
                __int64 ans=0;
//              printf("%d %d %d\n",dir[cur],dir[to],dir[end]);
                ans=(__int64)dir[cur]+dir[to]-2*dir[end];
                cur=to;
                 printf("%I64d\n",ans);
            }
            else {
                int i,tem;
                scanf("%d %d",&i,&tem);
                int change=tem-edge[(i-1)*2].w;
                edge[(i-1)*2].w=edge[ ((i-1)*2)+1 ].w=tem;
                int u=edge[(i-1)*2].to,v=edge[(i-1)*2+1].to;

//              printf("u,v:%d %d %d %d\n",u,v,rmq[first[u]],rmq[first[v]]);
                if(rmq[first[u]]<rmq[first[v]]) swap(u,v);
                work(u,v,change);
                //我们用dir 记录到根节点的距离 ,所以往深度大的节点改动就行了,深度小的本身就不改变
            }
        }
    }
    return 0;
}

http://acm.hdu.edu.cn/showproblem.php?pid=3078
题意:
给一个n,q
然后给出n个节点的权值
接下来n-1 行,每行给一条无向边连接两个节点。
然后q行
每行一个操作:
输入 三个数 a,b,c 若a=0 ,则将点b 的权值改为c
若a!=0 ,则操作是询问 从 b -c 路径上所有点的权值 第K大是多少

首先得明确一个观点:
这种题目 操作中加入了修改,所以你修改会影响到下面的询问,讲道理会用在线算法。

对于这个题:我一开始想用一个 set去存每一个节点到根节点的路径。 因为N=8W ,二维数组开不下来。 但是set 不可以放重复的元素。 然后我就卡住了。 想了半天觉得 不可能说用一个东西来存路径,因为内存必然不够。
但是我们只要知道路径,在线的提取出来不就好了吗?

我们可以多开一个数组pre, pre[i]就代表 i的上一个节点是多少, 当我们建好树之后, 如果询问 i 和 j的路径第K大的节点。我们就可以 k= lca(i,j)
然后我们吧 i ->k 和 j->k 路径上的点的权值都记录在数组里,就可以找到第k大点了

#include <cstdio>
#include <cmath>
#include <cstring>
#include <ctime>
#include <iostream>
#include <algorithm>
#include <set>
#include <vector>
#include <sstream>
#include <queue>
#include <typeinfo>
#include <fstream>
#include <map>
#include <stack>
typedef long long ll;
using namespace std;

const int MAXN=80010;
int rmq[2*MAXN]; //rmq数组, 记录每个节点在树中的深度
struct ST{
    int mm[2*MAXN];
    int dp[2*MAXN][20]; //dp 直接存下标
    void init(int n){
        mm[0]=-1;
        for(int i=1;i<=n;i++){
            mm[i]=((i&(i-1))==0)? mm[i-1]+1 :mm[i-1]; //???莫名其妙定义长度
            dp[i][0]=i; //初始化dp数组
        }
        for(int j=1;j<=mm[n];j++)   //可以看出来mm[n] 就是一个长度为n的序列j的最大值
            for(int i=1;i+(1<<j)-1 <=n ; i++){
                if(rmq[dp[i][j-1]] < rmq[dp[i+(1<<(j-1))][j-1] ]){
                    dp[i][j]=dp[i][j-1];
                }
                else
                    dp[i][j]=dp[i+(1<<(j-1))][j-1];
            }
    }
    int query(int a,int b){  //这个询问返回的是位置pos, F[pos]才是值
            if(a>b)
                swap(a,b);
            int k=mm[b-a+1];
            if(rmq[dp[a][k]] <= rmq[dp[b-(1<<k)+1][k]] ){
                return dp[a][k];
            }
            else
                return dp[b-(1<<k)+1][k];
    }
};
struct Edge{
    int to,next;
    int w; // 用w来记录权值:即到根部的距离。
};
Edge edge[MAXN*2];
int tot,head[MAXN];
int F[MAXN*2];   // 按顺序存储节点 ,下标从1开始,长度为2*n-1
int first[MAXN]; // i在F中第一次出现的位置
int cnt;
int fa[MAXN];  //  记录每个点的父亲节点,以此来找路径会特别方便
int w[MAXN];
ST st;
void init(){
    tot=0;
    memset(head,-1,sizeof(head));

}
void addedge(int u,int v){ //无向边自然加两次
    edge[tot].to=v;
    edge[tot].next=head[u];
    head[u]=tot++;
}
void dfs(int u,int pre,int dep){ //u起点,dep表示深度
     F[++cnt] = u;    // cnt就是这个节点的下标
     fa[u]=pre;
     rmq[cnt]=dep;    //rmq记录节点在树中的深度
     first[u]=cnt;       //这个first 不会被更新掉,因为我们把continue了回去的路
     for(int i=head[u];i!=-1; i=edge[i].next){
         int v=edge[i].to;
         if(v==pre) continue;

//       dir[v]=dir[u] + edge[i].w;
         dfs(v,u,dep+1);
         F[++cnt] = u;     //虽然continue回去的路,但是询问叶子节点还要返回:1-2-1 --....
         rmq[cnt] = dep;
     }
}
void LCA_init(int root ,int node_num){ //查询LCA前的初始化
    cnt=0;
    dfs(root,root,0);
    st.init(2*node_num-1); // 注意 ,这里dp的不是n,而是对F进行dp
}
int query_lca(int u,int v){   //查询lca(u,v)
    return F[st.query(first[u],first[v])];
}
bool flag[MAXN];
int main(){
  //  freopen("1.txt","r",stdin);
    int n,q;
    while(~scanf("%d %d",&n,&q)){
        init();
        memset(flag,0,sizeof(flag));
        for(int i=1;i<=n;i++)
            scanf("%d",&w[i]);
        for(int i=1;i<n;i++){
            int u,v;
            scanf("%d %d",&u,&v);
            addedge(u,v);
            addedge(v,u);
            flag[v]=1;
        }
        int root;
        for(int i=1;i<=n;i++)
                    if(!flag[i]){ //某个根
                        root=i;
                        break;
                    }
        LCA_init(root,n);
        int ans[MAXN];
        while(q--){
            int f,a,b;
            scanf("%d %d %d",&f,&a,&b);
            if(f==0){
                w[a]=b;
                continue;
            }
            int u=a;
            int v=b;
            int end=query_lca(u,v);
            int len=0;
            while(u!=end){
//              printf("fa=%d %d %d\n",fa[u],w[u],w[fa[u]]);
                ans[len++]=w[u];
                u=fa[u];
            }
            while(v!=end){
                ans[len++]=w[v];
                v=fa[v];
            }
            ans[len++]=w[end];
            sort(ans,ans+len);
//          printf("ans:");
//          for(int i=0;i<len;i++)
//              printf("%d ",ans[i]);
//          cout<<endl;
            if(f>len)
                printf("invalid request!\n");
            else
                printf("%d\n",ans[len-f]);
        }

    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值