cqyz oj | 树上差分 | 最近公共祖先 | 差分

  • Description

  给定含 n 个结点的无根树,有m个人在树上旅行,第i个人从u[i]出发,到v[i]结束。请你统计下面两个信息:

  1)、经过每个点的人数。

  2)、经过每条边的人数。

  • Input

  第一行包括两个正整数 n,m,表示树的节点数和人数,树结点从1到n编号。  接下来n-1行描述树边情况,其中第i行包含2个整数ai, bi,表示第i条双向边连接ai和bi。注意,边的编号按输入顺序从1..n-1编号。   接下来m行描述每个人的旅行信息:u[i]和v[i]。

  • Output

  第一行包含n个整数,第i个整数表示经过i号点的人数。  第二行包含n-1个整数,第i个整数表示编号为经过编号为i的边的人数。

  • Sample Input 1 
11 4
1 6
2 1
4 3
6 7
9 8
3 1
3 5
2 10
10 11
8 6
1 9
3 10
11 5
4 8
  • Sample Output 1
4 2 3 1 1 2 0 2 1 2 1
2 2 1 0 1 3 1 2 1 2
  • Hint

  1<n,m<=300000


对于一个人从 u 走到 v,则路径 u->LCA(u,v)->v 上的所有点经过的人数增加 1,路径上所有边上经过的人数也或增加 1。前面我们已经介绍这类问题提在序列上如何用差分思想来优化算法,但这里是树,能否利用这个思想呢?答案是肯定的!
1、树上差分数组
设差分数组:(下面所提到的权值表示经过人数)
A[i] 表示结点 i 的权值与其“所有儿子结点权值和”的差。
B[i] 表示边 fa[i]->i 的权值与所有i->son[i]的边权值和的差。
那么我们现在来看一次修改:一个人从 u 走到 v,差分数组该如何修改:
该人走过的路径为 u->z->v,那么
对于点权差分数组有:
A[u]++, A[v]++,
A[z]--, A[fa[z]]--;
对于边权差分数组有:
B[u]++, B[v]++;
B[z]=B[z]-2;
2、差分数组前缀和
设点权前缀和为 SA[i],边权前缀和为 SB[i],编号为 id的边权为 sum[id]
1)、计算经过点 i 的人数:
𝑆𝐴[𝑖] = 𝑆𝐴[𝑗1] + 𝑆𝐴[𝑗2] + ⋯ + 𝑆𝐴[𝑗𝑘] + 𝐴[𝑖]
2)、计算经过边 fa[i]->i 的人数:
𝑆𝐵[𝑖] = 𝑆𝐵[𝑗1] + 𝑆𝐵[𝑗2] + ⋯ + 𝑆𝐵[𝑗𝑘] + 𝐵[𝑖]
设这条边的编号为 id,则𝑆𝑢𝑚[𝑖𝑑] = 𝑆𝑏[i]
算法时间复杂度分析:
对于每一个从 u 到 v 旅行的人,需要用𝑂(𝑙𝑜𝑔2 𝑛)的时间复杂度计算 LCA(u,v),然后用 O(1)的时间
复杂度维护差分数组,所以时间总的时间复杂度为𝑂(𝑚 × 𝑙𝑜𝑔2 𝑛)

某些实现细节见代码:

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#define maxn 300005
#define maxm 600005
#define id(x) ((x+1)>>1)
using namespace std;
int fir[maxn], ne[maxm], to[maxm], id[maxm], np=0;
void add(int x,int y,int z){
    ne[++np] = fir[x];
    fir[x] = np;
    to[np] = y;
    id[np] = z;        //LCA若用倍增法在线算用宏定义那个id()算边的ID每次需要时计算有个点会被卡
}

int dep[maxn], fa[maxn][20];
void dfs(int u,int f,int d){
    dep[u] = d;
    fa[u][0] = f;
    for(int k = 1; k <= 18; k++){
        int j = fa[u][k-1];
        fa[u][k] = fa[j][k-1];
    }
    for(int i = fir[u]; i; i=ne[i]){
        int v = to[i];
        if(v != f) dfs(v, u, d+1);
    }
}

int LCA(int x,int y){
    if(dep[x] < dep[y]) swap(x, y);
    int j = dep[x] - dep[y];
    for(int k = 18; k >= 0; k--)
        if((1<<k)&j) x = fa[x][k];
    if(x == y) return x;
    
    for(int k = 18; k >= 0; k--)
        if(fa[x][k] != fa[y][k])
            x = fa[x][k], y = fa[y][k];
    return fa[x][0];
}

int n,m;
void data_in(){
    scanf("%d%d", &n, &m);
    for(int i=1, x, y; i<n; i++){
        scanf("%d%d", &x, &y);
        add(x, y, i); add(y, x, i);
    }
}

int A[maxn], B[maxn];
int SA[maxn], SB[maxn];
int sum[maxn];
void _DFS(int u,int f){
    SA[u] = A[u], SB[u] = B[u];
    for(int i=fir[u];i;i=ne[i]){
        int v = to[i];
        if(v == f) continue;
        _DFS(v, u);
        SA[u] += SA[v];
        SB[u] += SB[v];
        sum[id[i]] = SB[v];//!!!
    }
}

void solve(){
    dfs(1, 0, 0);
    int u, v, z;
    while(m--){
        scanf("%d%d",&u,&v);
        z = LCA(u, v);
        
        A[u]++; A[v]++;
        A[z]--; A[fa[z][0]]--;
        
        B[u]++; B[v]++;
        B[z]-=2;
    }
    _DFS(1, 0);

    for(int i=1;i<n;i++) printf("%d ",SA[i]);
    printf("%d\n", SA[n]);        //cqyz oj自带输出坑
    for(int i=1;i<n-1;i++) printf("%d ",sum[i]);
    printf("%d",sum[n-1]);
}

int main(){
    data_in();
    solve();
    return 0;
}
View Code

对于某数据卡在线的倍增算法,可以考虑用离线的tarjan算法,时间O(n+2*m)。
(以下代码部分WA,而且不知道为什么题库跑的时间比上面那个算法快不了多少,如果有大佬看出问题请在评论区找我orz)

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<vector>
#include<algorithm>
#define maxn 300005
#define maxm 600005
#define id(x) ((x+1)>>1)
using namespace std;
int fir[maxn], ne[maxm], to[maxm], np=0;
void add(int x,int y){
    ne[++np] = fir[x];
    fir[x] = np;
    to[np] = y;
}

vector<int> que[maxn];
int A[maxn], B[maxn];
int SA[maxn], SB[maxn];
int sum[maxn];
int vis[maxn], fa[maxn];

int bcj[maxn];
int Find(int x){return x == bcj[x] ? x : bcj[x] = Find(bcj[x]);}

int n,m;
void data_in(){
    scanf("%d%d", &n, &m);
    for(int i=1; i<=n; i++) bcj[i] = i, vis[i] = 0;
    for(int i=1, x, y; i<n; i++){
        scanf("%d%d", &x, &y);
        add(x, y); add(y, x);
    }
    while(m--){
        int u, v;
        scanf("%d%d",&u,&v);
        que[u].push_back(v);
        que[v].push_back(u);
    }
}

void tanjar(int u,int f){
    vis[u] = 1; fa[u] = f;
    for(int p=0, siz=que[u].size(), lca; p<siz; p++){
        int v = que[u][p];
        if(vis[v]){
            lca = Find(v);
            
            A[u]++; A[v]++;
            A[lca]--; A[fa[lca]]--;
            
            B[u]++; B[v]++;
            B[lca]-=2;
        }
    }
    for(int i=fir[u];i;i=ne[i]){
        int v=to[i];
        if(v == f) continue;
        tanjar(v, u);
        bcj[v] = u;
    }
}

void _DFS(int u,int f){
    SA[u] = A[u], SB[u] = B[u];
    for(int i=fir[u];i;i=ne[i]){
        int v = to[i];
        if(v == f) continue;
        _DFS(v, u);
        SA[u] += SA[v];
        SB[u] += SB[v];
        sum[id(i)] = SB[v];
    }
}

void solve(){
    tanjar(1, 0);
    _DFS(1, 0);
    
    for(int i=1;i<n;i++) printf("%d ",SA[i]);
    printf("%d\n", SA[n]);
    for(int i=1;i<n-1;i++) printf("%d ",sum[i]);
    printf("%d",sum[n-1]);
}

int main(){
    data_in();
    solve();
    return 0;
}
View Code
posted @ 2019-08-28 17:59  Deguassing-compass  阅读(174)  评论(0编辑  收藏  举报