BZOJ - 3757 树上莫队解决离线路径问题 & 学习心得

题意:给你一棵树,求u,v最短路径的XXX(本题是统计权值种类)

今天课上摸鱼学了一种有意思的处理路径方式(其实是链式块状树翻车了看别的),据说实际运行跑的比XX记者还快

大概就是像序列莫队那样

首先是对暴力查询的优化

第一关键字是块(树上分块),第二关键字是dfs序,这样保证了离线操作的下界最优

其次是转移的优化

我把大佬的话再转述一遍:

设\(S(u,v)\):\(u-v\)最短路径所覆盖的点集

\(S(u,v)=S(root,u)⊕S(root,v)⊕lca(u,v)\)

记\(T(u,v)=S(root,u)⊕S(root,v)\)

每次转移我们只考虑\(T\)的部分,\(lca\)单独处理

对于某一次距离为1的转移,如\(u→u‘\)

\(T(u,u‘)=S(root,u)⊕S(root,u‘)\)

\(T(u‘,v)=S(root,u‘)⊕S(root,v)\)

\(T(u‘,v)=S(root,u)⊕S(root,v)⊕S(root,u)⊕S(root,u‘)=T(u,v)⊕T(u,u‘)\)

得出结论\(T(u‘,v)=T(u,v)⊕T(u,u‘)\)

就是说转移的时候只需多处理\(u-u‘\)和\(v-v‘\)即可(推广后就是任意距离都可以),记得考虑每次单独处理的\(lca\)(先后翻转2遍标记)

那么再经过%hzwer的帖子学习后得出可能正确的代码(由于无法交题,没有验证正确性)

#include<bits/stdc++.h>
#define rep(i,j,k) for(register int i=j;i<=k;i++)
#define rrep(i,j,k) for(register int i=j;i>=k;i--)
#define erep(i,u) for(register int i=head[u];~i;i=nxt[i])
#define print(a) printf("%lld",(ll)(a))
#define println(a) printf("%lld\n",(ll)(a))
#define printbk(a) printf("%lld ",(ll)(a))
using namespace std;
const int MAXN = 3e4+11;
const int INF = 0x7fffffff;
typedef long long ll;
ll read(){
    ll x=0,f=1;register char ch=getchar();
    while(ch<‘0‘||ch>‘9‘){if(ch==‘-‘)f=-1;ch=getchar();}
    while(ch>=‘0‘&&ch<=‘9‘){x=x*10+ch-‘0‘;ch=getchar();}
    return x*f;
}
int to[MAXN<<1],nxt[MAXN<<1],head[MAXN],tot;
void init(){
    memset(head,-1,sizeof head);
    tot=0;
}
void add(int u,int v){
    to[tot]=v;
    nxt[tot]=head[u];
    head[u]=tot++;
}
int color[MAXN],belong[MAXN],depth[MAXN],dfn[MAXN];
int stk[MAXN],bit[32],limit,root,cnt,CLOCK,top;
int anc[MAXN][20];
bool vis[MAXN];
int ANS,cntNum[MAXN],ans[MAXN];
struct QQQ{
    int u,v,a,b,id;
    bool operator < (const QQQ &rhs) const{
        if(belong[u]!=belong[rhs.u]){
            return belong[u]<belong[rhs.u];
        }else{
            return dfn[v]<dfn[rhs.v];
        }
    }
}Q[MAXN];

int dfs(int u,int fa,int d){
    dfn[u]=++CLOCK;
    anc[u][0]=fa; depth[u]=d;
    rep(i,1,16){
        if(depth[u]<bit[i]) break;
        anc[u][i]=anc[anc[u][i-1]][i-1];
    }
    int num=0;
    erep(i,u){
        int v=to[i];
        if(v==fa) continue;
        num+=dfs(v,u,d+1);
        if(num>=limit){
            ++cnt;
            rep(i,1,num) belong[stk[top--]]=cnt;
            num=0;
        }
    }
    stk[++top]=u; num++;
    return num;
}
int lca(int u,int v){
    if(depth[u]<depth[v]) swap(u,v);
    int d=depth[u]-depth[v];
    for(int i=0;bit[i]<=d;i++){
        if(d>>i&1) u=anc[u][i];
    }
    for(int i=16;i>=0;i--){
        if(anc[u][i]!=anc[v][i]){
            u=anc[u][i];
            v=anc[v][i];
        }
    }
    if(u==v) return u;
    else return anc[u][0];
}
void rev(int u){
    if(!vis[u]){
        vis[u]=1;
        if(cntNum[color[u]]==0) ANS++;
        cntNum[color[u]]++;
    }else{
        vis[u]=0;
        if(cntNum[color[u]]==1) ANS--;
        cntNum[color[u]]--;
    }
}
void viss(int u,int v){
    while(u!=v){
        if(depth[u]>depth[v]) rev(u),u=anc[u][0];
        else rev(v),v=anc[v][0];
    }
}
int main(){
    int n,m;
    bit[0]=1;rep(i,1,30) bit[i]=bit[i-1]<<1;
    while(cin>>n>>m){
        init(); limit=sqrt(n)+1;
        rep(i,1,n) color[i]=read();
        rep(i,1,n){
            int u=read();
            int v=read();
            if(u*v==0) root=u|v;
            else add(u,v),add(v,u);
        }
        top=cnt=CLOCK=0;
        memset(anc,0,sizeof anc);
        dfs(root,0,1);
        if(top){
            cnt++;
            while(top) belong[stk[top--]]=cnt;
        }
        rep(i,1,m){
            Q[i].u=read();
            Q[i].v=read();
            Q[i].a=read();
            Q[i].b=read();
            Q[i].id=i;
        }
        sort(Q+1,Q+1+m); ANS=0;
        int t=lca(Q[1].u,Q[1].v);
        memset(vis,0,sizeof vis);
        viss(Q[1].u,Q[1].v);
        rev(lca(Q[1].u,Q[1].v));
        ans[Q[1].id]=ANS;
        rev(lca(Q[1].u,Q[1].v));
        if(cntNum[Q[1].a]&&cntNum[Q[1].b]&&Q[1].a!=Q[1].b){
            ans[Q[1].id]--;
        }
        rep(i,2,m){
            viss(Q[i-1].u,Q[i].u);
            viss(Q[i-1].v,Q[i].v);
            rev(lca(Q[i].u,Q[i].v));
            ans[Q[i].id]=ANS;
            if(cntNum[Q[i].a]&&cntNum[Q[i].b]&&Q[i].a!=Q[i].b){
                ans[Q[i].id]--;
            }
            rev(lca(Q[i].u,Q[i].v));
        }
        rep(i,1,m) println(ans[i]);
    }
    return 0;
}

原文地址:https://www.cnblogs.com/caturra/p/9048857.html

时间: 2024-10-04 20:57:46

BZOJ - 3757 树上莫队解决离线路径问题 & 学习心得的相关文章

bzoj 3757 树上莫队

感谢以下文章作者: http://blog.csdn.net/kuribohg/article/details/41458639 http://vfleaking.blog.163.com/blog/static/174807634201311011201627/ http://blog.csdn.net/jiangyuze831/article/details/41476865 http://hzwer.com/5259.html 做了树上的莫队,感觉对这个算法的思想理解更深了 先分块,不论怎

bzoj 3052 树上莫队 待修改

感谢: http://vfleaking.blog.163.com/blog/static/174807634201311011201627/ http://hzwer.com/5250.html 好吧,收获两点: 1.带修改,其实就是暴力,只是将同一块的查询再按照时间顺序排,这样就能减少在修改操作上“爬"的时间,其实就是利用了数据随机这个特点,可以构造数据来卡. 2.以前排序的方法是u按照块,v按照dfs序,这次两个都是按照块,其实差不多. 1 /***********************

bzoj 3757: 苹果树(树上莫队)

3757: 苹果树 Time Limit: 20 Sec  Memory Limit: 256 MB Submit: 1327  Solved: 510 [Submit][Status][Discuss] Description 神犇家门口种了一棵苹果树.苹果树作为一棵树,当然是呈树状结构,每根树枝连接两个苹果,每个苹果都可以沿着一条由树枝构成的路径连到树根,而且这样的路径只存在一条.由于这棵苹果树是神犇种的,所以苹果都发生了变异,变成了各种各样的颜色.我们用一个1到n之间的正整数来表示一种颜色

BZOJ 3757 苹果树 树上莫队

题目大意:给出一棵树,问任意两点之间有多少种不同的颜色,一个人可能会有色盲,会将A和B当成一种颜色. 思路:比较裸的树上莫队,写出来之后,很慢,怀疑是分块的缘故,然后果断找了当年比赛的标称交上去,瞬间rk1,大概看了一眼,他好像是直接用DFS序+曼哈顿距离最小生成树搞的,为什么会比分块快? 昨天下午看到这个题之后就一直在研究树上莫队的正确姿势,然后先写了树分块,后来看了很多牛人的SPOJ COT2的题解,后来又和同学探讨了好久才弄明白. 首先先将树分块,然后把区间排序,按照第一权值为左端点所在块

【BZOJ 3735】苹果树 树上莫队(树分块+离线莫队+鬼畜的压行)

学习了树上莫队,树分块后对讯问的$dfs序$排序,然后就可以滑动树链处理答案了. 关于树链的滑动,只需要特殊处理一下$LCA$就行了. 在这里一条树链保留下来给后面的链来转移的$now$的为这条树链上所有点除去$LCA$的颜色种数.因为如果要考虑$LCA$情况就太多了,不如单独考虑$LCA$. 转移后加上当前链的$LCA$进行统计,然后再去掉这个$LCA$更新一下$now$值给后面的链转移. 这都是我的理解,说的有点不清楚,具体请看vfk的题解 OTZ 虽然不是这道题,但是通过这篇博客学习树上莫

[BZOJ 3052] [wc2013] 糖果公园 【树上莫队】

题目链接:BZOJ - 3052 题目分析 这道题就是非常经典的树上莫队了,并且是带修改的莫队. 带修改的莫队:将询问按照 左端点所在的块编号为第一关键字,右端点所在的块为第二关键字,位于第几次修改之后为第三关键字 排序. 我们将块的大小设置在 n^(2/3) ,这样一共有 n^(1/3) 个块.最后算法的总复杂度会是 n^(5/3) . 每一次如何从上一个询问转移来呢? 假设上一个询问是 (lx, ly, lt) ,这次的询问是 (x, y, t) .t 代表是在第 t 次修改操作之后. 首先

树上莫队

树上莫队 引入 树上莫队看名字就知道,其实是把莫队搬到了树上.一般来说,这种问题有几个特征: 询问可以离线 答案并不好用树形DP或者数据结构维护,往往只能暴力跑到所有的点求. 我们拿一道例题:SP10707 COT2 这样的话,我们很容易就想到需要用莫队.可是莫队算法是基于一个序列的,我们怎么在树上跑莫队呢? 把树整成一个序列不就好了.一般来说,有dfs序和欧拉序两种情况. 什么?你问我什么是欧拉序? 欧拉序 操作:当访问到一个点的时候,把它加进序列.当离开这个点的时候,把它加进序列. 举个栗子

【BZOJ-3757】苹果树 块状树 + 树上莫队

3757: 苹果树 Time Limit: 20 Sec  Memory Limit: 256 MBSubmit: 1305  Solved: 503[Submit][Status][Discuss] Description 神犇家门口种了一棵苹果树.苹果树作为一棵树,当然是呈树状结构,每根树枝连接两个苹果,每个苹果都可以沿着一条由树枝构成的路径连到树根,而且这样的路径只存在一条.由于这棵苹果树是神犇种的,所以苹果都发生了变异,变成了各种各样的颜色.我们用一个到n之间的正整数来表示一种颜色.树上

SPOJ COT2 Count on a tree II (树上莫队,倍增算法求LCA)

题意:给一个树图,每个点的点权(比如颜色编号),m个询问,每个询问是一个区间[a,b],图中两点之间唯一路径上有多少个不同点权(即多少种颜色).n<40000,m<100000. 思路:无意中看到树上莫队,只是拿来练练,没有想到这题的难点不在于树上莫队,而是判断LCA是否在两点之间的路径上的问题.耗时1天. 树上莫队的搞法就是: (1)DFS一次,对树进行分块,分成sqrt(n)块,每个点属于一个块.并记录每个点的DFS序. (2)将m个询问区间用所属块号作为第一关键字,DFS序作为第二关键字