Codeforces Round #121 (Div. 1) C. Fools and Roads 经典的lca前缀和问题

题意:
给你一棵树,有n个点,并按顺序给你n-1条边, 然后有m次操作,每一次操作输入两个点a,b表示从a走到b点, 把路径中的所有边都染色一遍, 最后按顺序输出所有边被染色的次数。

第一眼看这道题就感觉这是一个很经典的问题。 首先我们知道对于a,b必然先找出ab的lca。 然后 朴素想法: 从a 向上到lca,从b向上到lca ,每条边的flag 都++,最后输出, 这种做法肯定是TLE 的,n=10W的数据量不会给你这么水过去。

那么我们考虑 先把所有的操作都做完,用什么东西记录下结果,然后再依次输出。

在纸上画一画再 思考一下可以发现前缀和可以解决此问题:(因为根节点到每一个节点只有一条路)
a->lca->b 上的边都会+1, 加入我们设 ans[i] 表示从根节点到i节点的上的所有边 染色的次数。 那么对于一次操作我们可以让: ans[a]++,ans[b]++, ans[lca]-2; ,表示从根节点到a节点上所有边都被染色一边,自然lca往上的边 都需要-2;

那么操作完之后我们如何去计算了?
自然我们需要按深度从大到小来计算边的染色次数。 我是先把所有的点按深度sort了一遍,然后对于以某个点v为终点的边 进行计算, 然后将ans[v]加到起点u上: ans[u]+=ans[v];


/*
                   _ooOoo_
                  o8888888o
                  88" . "88
                  (| -_- |)
                  O\  =  /O
               ____/`---'\____
             .'  \\|     |//  `.
            /  \\|||  :  |||//  \
           /  _||||| -:- |||||-  \
           |   | \\\  -  /// |   |
           | \_|  ''\---/''  |   |
           \  .-\__  `-`  ___/-. /
         ___`. .'  /--.--\  `. . __
      ."" '<  `.___\_<|>_/___.'  >'"".
     | | :  `- \`.;`\ _ /`;.`/ - ` : | |
     \  \ `-.   \_ __\ /__ _/   .-` /  /
======`-.____`-.___\_____/___.-`____.-'======
                   `=---='
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
         佛祖保佑       永无BUG
*/
/*
 * LCA 在线算法:dfs +ST(RMQ)
 * poj 1330 裸题
!!!!!!!!!!!!!!!!!!!!!!!!!!  求 i 深度 : rmq[first[i]]
!!!!!!!!!!!!!!!!!!!!!!!!!!  求 i 深度 : rmq[first[i]]
!!!!!!!!!!!!!!!!!!!!!!!!!!  求 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];
    }
};
ST st;
struct Edge{
    int to,next;
    int num;
};
Edge edge[MAXN*2];
int tot,head[MAXN];
int F[MAXN*2];
int first[MAXN];
int cnt;
int fa[MAXN];     // 将每个点的  fa 设位 末端为 i 的边 的 编号 tot
//int dir[N];      //保存每个点到树根的距离,很多问题中树边都有权值,会询问两点间的距离,
//如果树边没权值,相当于权值为1


void addedge(int u,int v){ //无向边自然加两次
    edge[tot].to=v;
    edge[tot].next=head[u];
    edge[tot].num=0;;
    head[u]=tot++;
}
void dfs(int u,int pre,int dep){ //u起点,dep表示深度
     F[++cnt] = u;    // cnt就是这个节点的下标
     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;
         if(fa[v]==-1) fa[v]=i;  // 将每个点的  fa 设位 末端为 i 的边 的 编号 tot
//       printf("fa:%d %d %d\n",v,fa[v],u);
         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,长度2n-1
}
int query_lca(int u,int v){   //查询lca(u,v)
    return F[st.query(first[u],first[v])];
}
bool flag[MAXN];
__int64 sum[MAXN*2];
__int64 ans[MAXN*2]; //真的蠢  ,不开二倍空间,真脑残
void init(){
    tot=0;
    memset(head,-1,sizeof(head));
    memset(flag,0,sizeof(flag));
    memset(sum,0,sizeof(sum));
    memset(ans,0,sizeof(ans));
    memset(fa,-1,sizeof(fa));
}
int pos[MAXN];
bool cmp(int a,int b){
    return rmq[first[a]]>rmq[first[b]];
}
int main(){
    //freopen("1.txt","r",stdin);
    int N;
    int u,v;
        scanf("%d",&N);
        init();
        for(int i=1;i<=N;i++) pos[i]=i;
        for(int i=1;i<N;i++){
            scanf("%d %d",&u,&v);
            addedge(u,v);
            addedge(v,u);
            flag[v]=true;
        }
        int root;
        for(int i=1;i<=N;i++)
            if(!flag[i]){ //某个根
                root=i;
                break;
            }
        LCA_init(root,N);
        int q;
        scanf("%d",&q);
        while(q--){
            scanf("%d %d",&u,&v);
            int LCA=query_lca(u,v);
            sum[u]++;
            sum[v]++;
            sum[LCA]-=2;
        }
        sort(pos+1,pos+N+1,cmp);
//      for(int i=1;i<=N;i++){
//          printf("%d %d\n",pos[i],sum[pos[i]]);
//      }
        for(int k=1;k<=N;k++){
            int end=pos[k];
            int i=fa[end];
            int sta=edge[i^1].to;
            if(i%2==1) i--;
//          printf("%d %d %d\n",sta,end,i);
            sum[sta]+=sum[end];
            if(end!=root) ans[i]=sum[end];
        }
         for(int i=0;i<2*(N-1);i+=2){
             printf("%I64d ",ans[i]);
         }
         printf("\n");

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值