SP10707 COT2-Count on a tree II (Find the number of different colors of the two-point path in the tree + the Mo team on the tree)

https://www.luogu.com.cn/problem/SP10707


Mo team board on the tree.

Teacher Luo's thinking:


1. Convert the nodes of the tree to a one-dimensional array using Eulerian order.
  Traverse the nodes of the tree using DFS. There are two traversal methods to obtain two Euler orders:
  (1) Enter the sum at each node for the first time Add to the sequence for the last time out;
  (2) Add it to the sequence every time a node is encountered.
  The Euler order of the first form is used here. In the example below, the Euler order: {1, 2, 2, 3, 5, 5, 6, 6, 7, 7, 3, 4, 8, 8, 4, 1}.

Figure 8 A tree

  What are the nodes of the path on (u, v)? First calculate the lca(u, v) ( nearest common ancestor ) of u and v , and then discuss two cases:
  (1) lca(u, v) = u or lca(u, v) = v, that is, u is in v In the subtree of u, or v in the subtree of u. For example, u = 1, v = 6, the interval is {1, 2, 2, 3, 5, 5, 6}, the node {2, 5} that appears twice does not belong to this path, because it comes in and goes out Up. The node that appears only once belongs to this path, namely {1, 3, 6}.
  (2) lca(u, v) ≠ u and lca(u, v) ≠ v, that is, neither u nor v are in each other's subtree. At this time, the path between u and v needs to pass through their lca, but lca does not appear in the Euler order interval of u and v and needs to be added. For example, u = 5, v = 8, the interval is {5, 6, 6, 7, 7, 3, 4, 8}, remove the node {6, 7} that appears twice, leaving {5, 3, 4 , 8}, plus their lca = 1, get the path {5, 3, 4, 8, 1}. Another example is u = 5, v = 7, the interval is {5, 6, 6, 7}, remove 6, leave {5, 7}, and add their lca = 3, we get the path {5, 7, 3 }.
2. Solving steps of this problem
  (1) Find the Euler order of the tree to obtain a one-dimensional array; find the lca of any two points. When coding, use tree chain division (do DFS twice) to find Euler order and lca.
  (2) Treat the query (u, v) of the title as a query on a one-dimensional array. The question requires querying different colors in (u, v). First, check the nodes that appear only once in the interval (u, v), and add the lca of u and v to get all the nodes on the path. Then, in these Statistics in the node only appear once.
  (3) Use the Mo team algorithm to process all the queries offline, and then output them together. Note that when partitioning, the scale of this question is 2n, because each node appears twice in the Euler order; in addition, the color value of each node is very large and needs to be discretized.


Previously, the weight of the subtree was maintained in the dfs order, and the dfs order was transformed into a continuous sequence of subtrees, so that the range of the continuous sequence of the subtree on the corresponding tree was modified in intervals, and the line segment tree was used for maintenance.

Topic link: https://vjudge.net/problem/HDU-3974/origin (dfs sequence upper line segment tree)

This question is the preprocessing Euler order in dfs. Then use Euler order to discuss the relationship of the sequence obtained without being in the subtree. Go to linear and then maintain with nsqrt(n).

A better way:

How to ignore the points that appear twice in the interval, and record one more vis[x] to indicate whether the tree node of x has been added. If vis[x]=0 during each processing, you need to add a node; if vis[ x]=1, the node needs to be deleted, and vis[x] is XORed by 1 after each processing.

#include<iostream>
#include<vector>
#include<queue>
#include<cstring>
#include<cmath>
#include<map>
#include<set>
#include<cstdio>
#include<algorithm>
#define debug(a) cout<<#a<<"="<<a<<endl;
using namespace std;
const int maxn=4e5+100;
typedef int LL;
struct Query{
    LL l,r,ll,rr,lca;
    LL id;
}q[100010];
bool vis[maxn];
LL a[maxn],b[maxn];
LL cnt[maxn];
LL answer[maxn];
LL sum=0;
///莫队部分
bool cmp(Query A,Query B){
    if(A.ll!=B.ll){
        return A.ll<B.ll;
    }
    if(A.ll&1) return A.rr>B.rr;
    else return A.rr<B.rr;
}
void add(LL x){
    cnt[a[x]]++;
    if(cnt[a[x]]==1) sum++;
}
void del(LL x){
    cnt[a[x]]--;
    if(cnt[a[x]]==0) sum--;
}
void cal(LL x){
    ///每个节点都有一个颜色
    (!vis[x])?add(x):del(x);
    vis[x]^=1;
}
///树链剖分部分(求LCA)
LL times=0;
LL siz[maxn],top[maxn],dep[maxn],fa[maxn],son[maxn];
vector<LL>g[maxn];
LL numid[maxn*2],st[maxn],ed[maxn];
void predfs(LL u,LL father){
    siz[u]=1;dep[u]=dep[father]+1;
    fa[u]=father;
    st[u]=++times;
    numid[times]=u;
    for(LL i=0;i<g[u].size();i++){
        LL v=g[u][i];
        if(v==father) continue;
        predfs(v,u);
        siz[u]+=siz[v];
        if(siz[v]>siz[son[u]]){
            son[u]=v;
        }
    }
    ed[u]=++times;numid[times]=u;///欧拉序
}
void dfs(LL u,LL topx){
     top[u]=topx;
     if(!son[u]) return;
     dfs(son[u],topx);
     for(LL i=0;i<g[u].size();i++){
        LL v=g[u][i];
        if(v==fa[u]||v==son[u]) continue;
        dfs(v,v);
     }
}
LL getLCA(LL u,LL v){
    while(top[u]!=top[v]){
        if(dep[top[u]]<dep[top[v]]) swap(u,v);
        u=fa[top[u]];
    }
    if(dep[u]>dep[v]) swap(u,v);
    return u;
}
int main(void)
{
  cin.tie(0);std::ios::sync_with_stdio(false);
  LL n,m;cin>>n>>m;
  for(LL i=1;i<=n;i++){
    cin>>a[i];b[i]=a[i];
  }
  sort(b+1,b+1+n);
  LL siz=unique(b+1,b+1+n)-b-1;
  for(LL i=1;i<=n;i++){
    a[i]=lower_bound(b+1,b+1+siz,a[i])-b;///离散化
  }
  for(LL i=1;i<n;i++){
    LL u,v;cin>>u>>v;
    g[u].push_back(v);
    g[v].push_back(u);
  }
  predfs(1,0);
  dfs(1,1);
  LL block=sqrt(n*2);
  for(LL i=1;i<=m;i++){
    LL u,v;cin>>u>>v;
    if(st[u]>st[v]) swap(u,v);///先被访问的放前面
    LL LCA=getLCA(u,v);
    ///u和v在同一个子树内
    if(LCA==u||LCA==v){
        q[i].id=i;
        q[i].l=st[u];q[i].r=st[v];
        q[i].ll=(st[u]-1)/block+1;///l端点分到的块的编号
        q[i].rr=(st[v]-1)/block+1;///r端点分到的块的编号
        q[i].lca=0;
    }
    ///u和v不在同一棵子树内
    else{
        q[i].id=i;
        q[i].l=ed[u];q[i].r=st[v];
        q[i].ll=(ed[u]-1)/block+1;q[i].rr=(st[v]-1)/block+1;
        q[i].lca=LCA;///最后要加上LCA的贡献
    }
  }
  LL L=1;LL R=0;
  sort(q+1,q+1+m,cmp);
  for(LL i=1;i<=m;i++){
     while(L<q[i].l) cal(numid[L++]);///该树的节点
     while(L>q[i].l) cal(numid[--L]);
     while(R<q[i].r) cal(numid[++R]);
     while(R>q[i].r) cal(numid[R--]);
     if(q[i].lca){
        cal(q[i].lca);
     }
     answer[q[i].id]=sum;
     if(q[i].lca){
        cal(q[i].lca);
     }
  }
  for(LL i=1;i<=m;i++){
    cout<<answer[i]<<endl;
  }
return 0;
}

 

Guess you like

Origin blog.csdn.net/zstuyyyyccccbbbb/article/details/110251080