SP10707 COT2 - Count on a tree II (树上莫队)

Posted 1625--h

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SP10707 COT2 - Count on a tree II (树上莫队)相关的知识,希望对你有一定的参考价值。

参考博客

对于树上的路径询问问题

  • O(1)的时间加入或删除一个点的贡献 -> \(O(n\sqrt n)\)的复杂度求出所有询问的答案

对树上的结点进行分块,离线询问后排序,顺序遍历暴力转移路径(转移时加入或删除路径上的点的贡献即可)。

关于转移路径:首先定义路径:设\(T_u\)\(u\) 到根的路径上边的集合,那么\(u\)\(v\) 的路径上的边的集合就是\(T_u \triangle T_v\) (\(\triangle\) 是对称差)。要从\(u\rightarrow v\) 转移到 \(u'\rightarrow v'\) 等价于

\[T_u \triangle T_v\rightarrow T_u'\triangle T_v' \]

根据对称差的性质\(T_u\triangle T_u\triangle T_u' = T_u'\) 所以只需要:

\[T_u \triangle T_v \triangle (T_u\triangle T_u')\triangle (T_v\triangle T_v') = T_u'\triangle T_v'\]

体现在程序上就是从\(u\rightarrow v\) 转移到\(u' \rightarrow v'\)时,暴力遍历路径\(u\rightarrow u'\)和路径\(v\rightarrow v'\)上的边,如果一条边已经加入,那么删除它,如果没有加入,就加入它。这样就完成了对称差运算。

复杂度分析:设树分块大小为S,每次转移\(u\rightarrow u'\)在块内的转移路径长度是\(O(S)\)的,而\(v\rightarrow v'\)的转移可以按照\(dfs\)序来递增,这样复杂度就是\(O(ms+n^2\over S)\)

\(S=\sqrt n\)时最优

例题 题目传送门

代码

#include <bits/stdc++.h>
using namespace std;
const int N = 40010;
const int M = 100010;
vector<int> G[N],alls;
int be[N],f[N][19],n,m,ans[M],dep[N],u=1,v=1,sum[N],vis[N],a[N],base,cnt;
int res;
int st[N],top;
struct Query
    int l,r;
    int id;
q[M];
void dfs(int x,int fa = -1)
    for(int i=1;i<18;i++)
        f[x][i] = f[f[x][i-1]][i-1];
    int bottom = top;
    for(int i = 0;i<G[x].size();i++)
        int y = G[x][i];
        if(y == fa)continue;
        dep[y] = dep[x] + 1;
        f[y][0] = x;
        dfs(y,x);
        if(top - bottom >= base)
            cnt++;
            while(top != bottom)
                be[st[top--]] = cnt;
            
        
    
    st[++top] = x;

bool cmp(Query a,Query b)
    return be[a.l] == be[b.l] ? be[a.r] < be[b.r] : be[a.l] < be[b.l];

int LCA(int x,int y)
    if(dep[x] > dep[y])swap(x,y);
    for(int i=18;i>=0;i--)if(dep[x] <= dep[f[y][i]]) y = f[y][i];
    if(x == y)return x;
    for(int i=18;i>=0;i--)if(f[x][i] != f[y][i])x = f[x][i],y = f[y][i];
    return f[x][0];

void Run(int u)
    if(vis[u] == 1)
        vis[u] = 0;
        if(--sum[a[u]] == 0)res--;
    
    else
        vis[u] = 1;
        if(sum[a[u]]++ == 0)res++;
    

void move(int x,int y)
    if(dep[x] < dep[y])swap(x,y);
    while(dep[x] > dep[y])Run(x),x = f[x][0];
    while(x != y)Run(x),Run(y),x = f[x][0],y = f[y][0];

int main()
    scanf("%d%d",&n,&m);
    base = sqrt(n);
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]);
        alls.push_back(a[i]);
    
    /*离散化*/
    sort(alls.begin(),alls.end());
    alls.erase(unique(alls.begin(),alls.end()),alls.end());
    for(int i=1;i<=n;i++)
        int id = lower_bound(alls.begin(),alls.end(),a[i]) - alls.begin() + 1;
        a[i] = id;
    
    //存树
    for(int i=1;i<n;i++)
        int x,y;scanf("%d%d",&x,&y);
        G[x].push_back(y);
        G[y].push_back(x);
    
    dep[1] = 1;//这里如果不单独设为wa
    dfs(1);
    while(top)be[st[top--]] = cnt;
    //存询问
    for(int i=1;i<=m;i++)
        scanf("%d%d",&q[i].l,&q[i].r);
        q[i].id = i;
    
    sort(q+1,q+1+m,cmp);
    for(int i=1;i<=m;i++)
        if(u != q[i].l)move(u,q[i].l);u=q[i].l;
        if(v != q[i].r)move(v,q[i].r);v=q[i].r;
        int anc = LCA(u,v);
        Run(anc);//单独考虑LCA
        ans[q[i].id] = res;
        Run(anc);//
    
    for(int i=1;i<=m;i++)printf("%d\n",ans[i]);
    return 0;

以上是关于SP10707 COT2 - Count on a tree II (树上莫队)的主要内容,如果未能解决你的问题,请参考以下文章

SPOJ10707COT2 - Count on a tree II

SPOJ10707 COT2 - Count on a tree II 树上莫队

[SPOJ10707]Count on a tree II

「SPOJ10707」Count on a tree II

spoj COT2 - Count on a tree II

SPOJ - COT2 Count on a tree II