码迷,mamicode.com
首页 > 其他好文 > 详细

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

时间:2019-08-09 21:41:23      阅读:161      评论:0      收藏:0      [点我收藏+]

标签:for   块大小   std   return   names   const   等价   需要   没有   

参考博客

对于树上的路径询问问题

  • 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 (树上莫队)

标签:for   块大小   std   return   names   const   等价   需要   没有   

原文地址:https://www.cnblogs.com/1625--H/p/11329565.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!