[spoj10707]Count on a tree II 解题报告

一开始不知道这是主席出的神题,不小心点开了。。结果做了1天(想了半天+写了半天)。
我是学莫队的时候在某大神的莫队课件里看到这道题的,他说可以用莫队做。(但这真的算莫队么?)
这道题非常奇怪,就是把 HH的项链 搬到了树上,但是这样就变得好难。。

我一开始的想法是就是把dfs序分成大小为S的块,然后就是 O(N3S2+MS) ,这样显然块越大越好,所以卡着内存线S最大可以开到800,时间复杂度大约就是 108 ,然而常数巨大,我本机一个点要跑4s左右。。

然后我无奈之下只好选择用bitset强艹。
考虑bitset的话,我们的目的就是要搞出(u,v)的bitset的并。我们考虑如何搞出前缀和的权值bitset,可以把权值按分成大小为S的块,然后用树上主席树,每次修改一个块。但是问题在于这个前缀和是没法减的,所以我们只需在外面在套一层点分就可以了。
所以最终就是点分+树上主席树+分块+bitset?
还是比较好写的。。
时间复杂度是 O(Nlog2N(log2NS+S32)+M(NS+N32)) (因为要卡内存所以算的超级细啦~),显然这个S也是越大越好的,所以卡内存令S=5000,时间复杂度就是 2108 ,但是由于主要是在用bitset,所以还可以的啦~
(以上是因为我比较脑残,感谢lct1999指正)
但是注意到 NS 只有8,所以 NS logNS 并没有差别。。所以直接裸上可持久化分块就行。。时间复杂度是 O(NlogN(NS+S32)+M(NS+N32))

最后口胡一下该怎么用分块/莫队做这个题。(因为感觉麻烦+常数大所以懒得写了)
将dfs序分成大小为S的块,考虑统计每种权值出现的次数,我们可以用 O(NNS) 求出每对块之间的答案,但问题在于记状态。注意到每个颜色在两点之间的路径的出现次数等于两者前缀和的减去lca的前缀和减去lca的父亲的前缀和,所以我们可以对每个块记前缀和的颜色状态,然后应付询问的时候我们可以现求lca的状态 O(S) 。这样就得到两个块端点之间的状态了,然后我们在 O(S) 转移到两个块内的点之间的状态就行了。
时间复杂度 O(N2S+MS)O(NM) 107 ,但感觉应该和上面那个常数小的 108 跑起来差不多的。(当然也可能快很多。。)

膜拜一下主席的做法:orz fotile

orz lct1999的做法:考虑树分块,按dfs/bfs序逆序建块,如果当前节点的子树中最远的没被分到其他块里的节点距离当前节点为B,那么就把这个子树中所有没被分到其他块里的节点都收成一块。最后如果1没有被收成一块,就把1收一下。这样块的大小至少为B,最多有 NB 块,块中任意两点间的距离不会超过 2B 。然后预处理每个块端点到每个节点的答案,用可持久化分块获得状态。然后每次暴力块端点深度比较深的那个块里的节点到端点的路径。(我感觉这样写比较简单,因为可能出现lca(u,v)在u或v的块里的情况,如果预处理每对块端点的答案的话处理这种情况就会变得非常麻烦。。)但是常数好像比较大。。我写了一发T的飞起。。

这题还有个很蛋疼的地方,就是数据中的样例那个点后面多了个空格,必须要特判,否则就会pe。(结果我在discuss里说了以后lavendir把那个空格秒删了。。)

代码:

#include<cstdio>
#include<cstring>
#include<cmath>
#include<iostream>
#include<algorithm>
#include<bitset>
#include<cassert>
using namespace std;
const int N=4e4+5,M=1e5+5;

int w[N],hash[N],htot;

int next[N<<1],succ[N<<1],ptr[N],etot=1;
void addedge(int from,int to){
    next[etot]=ptr[from],ptr[from]=etot,succ[etot++]=to;
}

const int D=15;
//点分深度, 点分时大小为1的树就不算了。

const int S=5000;
//const int S=10;
bitset<S> b[N*D];
int bcnt[N*D];
int btot=1;

int bpos[D][N][N/S];


int size[N],q[N],fa[N];
bool flag[N];
int center[N][D];
void bin(int node,int depth){
    //求重心
    int h=0,t=1;
    q[0]=node,fa[node]=0;
    for(;h!=t;++h)
        for(int i=ptr[q[h]];i;i=next[i])
            if(succ[i]!=fa[q[h]]&&!flag[succ[i]]){
                fa[succ[i]]=q[h];
                q[t++]=succ[i];
            }
    if(t==1)return;
    /*if(depth==D){ cout<<node<<":"<<depth<<endl; } assert(depth<D);*/
    for(int i=t;i--;){
        size[q[i]]=1;
        for(int j=ptr[q[i]];j;j=next[j])
            if(succ[j]!=fa[q[i]]&&!flag[succ[j]])
                size[q[i]]+=size[succ[j]];
    }
    for(int i=1;i;)
        for(i=ptr[node];i;i=next[i]){
            //cout<<succ[i]<<":"<<size[succ[i]]<<endl;
            if(succ[i]!=fa[node]&&!flag[succ[i]]&&size[succ[i]]>t>>1){
                node=succ[i];
                break;
            }
        }

    //printf("-----bin(%d,%d,%d)----\n",node,t,depth);

    //work
    h=0,t=1;
    q[0]=node;
    fa[node]=0;
    for(;h!=t;++h){
        memcpy(bpos[depth][q[h]],bpos[depth][fa[q[h]]],sizeof(bpos[0][0]));
        if(b[bpos[depth][q[h]][w[q[h]]/S]][w[q[h]]%S]==0){
            b[btot]=b[bpos[depth][q[h]][w[q[h]]/S]];
            b[btot][w[q[h]]%S]=1;
            bcnt[btot]=bcnt[bpos[depth][q[h]][w[q[h]]/S]]+1;
            bpos[depth][q[h]][w[q[h]]/S]=btot++;
        }

        center[q[h]][depth]=node;
        for(int i=ptr[q[h]];i;i=next[i])
            if(succ[i]!=fa[q[h]]&&!flag[succ[i]]){
                fa[succ[i]]=q[h];
                q[t++]=succ[i];
            }
    }
    //to next level
    flag[node]=1;
    ++depth;
    for(int i=ptr[node];i;i=next[i])
        if(!flag[succ[i]])
            bin(succ[i],depth);
}
int main(){
    //cout<<"Memory="<<(sizeof(size)*3+sizeof(center)+sizeof(flag)+sizeof(ls)*2+sizeof(root)+sizeof(bcnt)+sizeof(b)+sizeof(next)*2+sizeof(ptr)*3>>20)<<endl;
    //return 0;

    freopen("bzoj_2589.in","r",stdin);
    //freopen("bzoj_2589.out","w",stdout);
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;++i){
        scanf("%d",w+i);
        hash[i-1]=w[i];
    }
    sort(hash,hash+n);
    htot=unique(hash,hash+n)-hash;
    for(int i=n;i;--i)w[i]=lower_bound(hash,hash+htot,w[i])-hash;
    --htot;

    //for(int i=1;i<=n;++i)printf("w[%d]=%d ",i,w[i]);
    //puts("");

    int u,v;
    for(int i=n;--i;){
        scanf("%d%d",&u,&v);
        addedge(u,v),addedge(v,u);
    }

    bin(1,0);
    for(int i=n;i;--i)
        for(int j=D-1;!center[i][j];--j)
            center[i][j]=i;

    int lastans=0;
    int depth;
    while(m--){
        scanf("%d%d",&u,&v);
        u^=lastans;
        if(u==v){
            lastans=1;
            puts("1");
            continue;
        }
        depth=D-1;
        while(center[u][depth]!=center[v][depth]){
            --depth;
            //cout<<depth<<":"<<center[u][depth]<<" "<<center[v][depth]<<endl;
            //assert(depth>=0);
        }
        //cout<<depth<<":"<<center[u][depth]<<endl;
        lastans=0;
        for(int i=N/S;i--;)
            if(bpos[depth][u][i]==0)lastans+=bcnt[bpos[depth][v][i]];
            else if(bpos[depth][v][i]==0)lastans+=bcnt[bpos[depth][u][i]];
            else lastans+=(b[bpos[depth][u][i]]|b[bpos[depth][v][i]]).count();
        printf("%d\n",lastans);
    }
}

总结:
①链剖可以将树上路径变成区间,点分可以将树上路径变成两个前缀,主席算法可以将树上路径变成一个从祖先到当前节点的路径+一坨两边的点,最万能的搞法——树分块!
②生成数据的时候一定要试试极限数据!(权值开最大,树变链)
③从一个节点出发去找重心的时候,如果重心在这个儿子里等价于这个儿子的 size>n2 ,而不是大于等于。
④dfs序+分块\莫队 是可以维护路径的。(比如说max什么的)
⑤一定要注意强制在线的问题。(我在本机拍是不强制在线的。。然后交的时候经常忘了改,re了好几遍。)
⑥调试分块的时候最好把块的大小开成3或4这样的拍。

你可能感兴趣的:(bitset,分块,dfs序,主席树,点分治)