SPOJ COT2 Count on a tree II [树上莫队]

题意:给出一棵树,共有N个节点,每个节点拥有一个编号和权值,询问M次,每次询问给出两个节点的编号,问这条路径上的所有节点有几个不同的权值。

范围:N<=4W M<=10W 权值<=10的9次

解法:树上莫队的经典题,一开始自己推,分块部分出错了,导致一直TLE....

  实际上应该在搜索时定义一个SIZE,每次搜索子节点,SZIE加上子节点分块后剩下的节点,如果SIZE大于了根号N,就分块,否则把自己这个节点入栈,返回SIZE

  询问排序时,要根据x,y的块来排序,这样可以让我每次拨动节点的时候,保证最多移动根号N个。

  转移部分,可以考虑将X拨动到NX位置,那么NX向上走到LCA,X走到LCA,将路径取反即可(即存在性取反,这样走过两边的路就消掉了,因为X,Y两个节点走过相同的部分是不需要走的,也就是根节点到LCA(X,Y)的部分是不需要走的);

  由于我是使用VIS【i】,表示i节点向上的边的存在性,所以vis[  LCA(x,y) ] 的存在性可能为0,但它又是必须取的,所以特判一下即可。

  其他细节的实现,可以看代码:

AC代码(3秒左右):

#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<math.h>
#include<iostream>
#include<stdlib.h>
#include<set>
#include<map>
#include<queue>
#include<vector>
#include<bitset>
#pragma comment(linker, "/STACK:1024000000,1024000000")
template <class T>
bool scanff(T &ret){ //Faster Input
    char c; int sgn; T bit=0.1;
    if(c=getchar(),c==EOF) return 0;
    while(c!='-'&&c!='.'&&(c<'0'||c>'9')) c=getchar();
    sgn=(c=='-')?-1:1;
    ret=(c=='-')?0:(c-'0');
    while(c=getchar(),c>='0'&&c<='9') ret=ret*10+(c-'0');
    if(c==' '||c=='\n'){ ret*=sgn; return 1; }
    while(c=getchar(),c>='0'&&c<='9') ret+=(c-'0')*bit,bit/=10;
    ret*=sgn;
    return 1;
}
#define inf 1073741823
#define llinf 4611686018427387903LL
#define PI acos(-1.0)
#define lth (th<<1)
#define rth (th<<1|1)
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define drep(i,a,b) for(int i=a;i>=b;i--)
#define gson(i,root) for(int i=ptx[root];~i;i=ed[i].next)
#define tdata int testnum;scanff(testnum);for(int cas=1;cas<=testnum;cas++)
#define mem(x,val) memset(x,val,sizeof(x))
#define mkp(a,b) make_pair(a,b)
#define findx(x) lower_bound(b+1,b+1+bn,x)-b
#define pb(x) push_back(x)
using namespace std;
typedef long long ll;

const int NN=40100;

int n,m;
int a[NN],b[NN],bn; //b[]用来离散化a[]

int ans[100100],sum,cot[NN];
bool vis[NN];
//vis[]表示节点向上连边的存在性,cot[i]记录i的出现次数;

int f[NN][22],dep[NN]; //lca相关数组
int ptx[NN],lnum=0; //图相关数组
int top,stk[NN],block[NN],bcnt,bsize; //分块相关数组

struct edge{
    int v,next;
}ed[NN*2];
inline void addline (int x,int y){
    ed[lnum].v=y;
    ed[lnum].next=ptx[x];
    ptx[x]=lnum++;
}

struct query{
    int x,y,idx;
}q[100100];
bool cmp (query x,query y){ //树上节点按块排序,保证相邻节点移动距离至多根号N
    if(block[x.x]==block[y.x])return block[x.y]<block[y.y];
    return block[x.x]<block[y.x];
}

// lca \ block
inline int lca(int x,int y){
    if(dep[x]<dep[y])swap(x,y);
    drep(i,16,0)if(dep[f[x][i]]>=dep[y])x=f[x][i];
    if(x==y)return x;
    drep(i,16,0)if(f[x][i]!=f[y][i])x=f[x][i],y=f[y][i];
    return f[x][0];
}
inline void add_block(int cnt) {
    bcnt++;
    while(cnt--) block[stk[top--]] = bcnt;
}
int dfs(int x,int fa)
{
    int sz=0;
    dep[x]=dep[fa]+1;//dep数组初始化
    gson(i,x){
        int y=ed[i].v;
        if(y==fa)continue;
        f[y][0]=x;
        sz+=dfs(y,x);
        if(sz>=bsize)add_block(sz),sz=0;
        //如果范围达到了上限,划分block
    }
    stk[++top]=x;
    return sz+1;
}
void init_lca_block(){
    bsize = max(1, (int)sqrt(n));
    add_block(dfs(1, 0)); 
    rep(k,1,16)  
    rep(i,1,n)f[i][k]=f[f[i][k-1]][k-1];
    //使用前提,dep[0]为0
}

//solve
inline void change(int x){ //此节点向上的连边存在性改变
    vis[x]^=1;
    if(vis[x]&&++cot[a[x]]==1)sum++;
    if(!vis[x]&&--cot[a[x]]==0)sum--;
}
inline void update(int x,int y){ //x->y路径存在性改变
    if(dep[x]<dep[y])swap(x,y);
    while(dep[x]!=dep[y])change(x),x=f[x][0];
    while(y!=x){
        change(x);x=f[x][0];
        change(y);y=f[y][0];
    }
}
void solve(){
    int x=1,y=1;//x,y指向根节点
    rep(i,1,m){
        update(x,q[i].x);x=q[i].x;//把x移到询问区间的x
        update(y,q[i].y);y=q[i].y;//把y移到询问区间的y
        int lcaxy=lca(x,y);
        if(cot[a[lcaxy]]==0)ans[q[i].idx]=sum+1;//如果根节点没有被计数
        else ans[q[i].idx]=sum;
    }
    rep(i,1,m)printf("%d\n",ans[i]);
}

void init(){
    bn=sum=lnum=0;
    mem(cot,0);
    mem(vis,0);
    mem(ptx,-1);
}

int main(){
    init();
    scanf("%d%d",&n,&m);
    //读入并离散化a[]
    rep(i,1,n)scanf("%d",&a[i]),b[++bn]=a[i]; 
    sort(b+1,b+1+bn);
    bn=unique(b+1,b+1+bn)-b-1;
    rep(i,1,n)a[i]=findx(a[i]);
    //读入边,并初始化lca和block
    int x,y;
    rep(i,1,n-1){
        scanf("%d%d",&x,&y);
        addline(x,y);
        addline(y,x);
    }
    init_lca_block();
    //读入询问,排序后用莫队算法求解
    rep(i,1,m){
        scanf("%d%d",&q[i].x,&q[i].y);
        q[i].idx=i;
        if(block[q[i].x]>block[q[i].y])swap(q[i].x,q[i].y);
    }
    sort(q+1,q+1+m,cmp);
    solve();
    return 0;
}

  

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值