• SP10707 COT2


    裸的树上莫队

    其实和普通莫队上一样的,只不过我们要把树转化为线性结构,这就需要欧拉序,我们从根对这棵树进行(dfs),点进栈时记一个时间戳(st),出栈时再记一个时间戳(ed),画个图理解一下

    fasdfa

    这棵树的欧拉序为((1,2,4,5,5,6,6,7,7,4,2,3,3)),那么每次询问的节点(u,v)有两种情况

    1. (u)(v)的子树中((v)(u)的子树中同理),比如(u=6,v=2),我们拿出((st[2],st[6]))这段区间((2,4,5,5,6))(5)出现了两次,因为搜索的时候(5)不属于这条链,所以进去之后就出去了,而出现一次的都在这条链上,就都可以统计

    2. (u)(v)不在同一个子树中,比如(u=5,v=3),这次拿出((ed[5],st[3]))这段区间((5,6,6,7,7,4,2,3)),要保证(st[u]<st[v]),出现两次的可以忽略,然而这次只统计了(5,4,2,3),所以最后再统计上(lca)就好了

    • 至于如何忽略掉区间内出现了两次的点,这个很简单,我们多记录一个(use[x]),表示(x)这个点有没有被加入,每次处理的时候如果(use[x]=0)则需要添加节点;如果(use[x]=1)则需要删除节点,每次处理之后都对(use[x])异或(1)就可以了

    • 而欧拉序可以用树剖来求,(lca)也就求出来了,非常的方便

    • 排序的话没有区别,可以普通排序,也可以奇偶性排序

    • 因为(st,ed)的大小都是(n),所以取块的大小时要用(2n),而不是(n)

    • 最后要注意的一点就是这个题权值比较大,需要离散化

    Code

    #include <iostream>
    #include <algorithm>
    #include <cstdio>
    #include <cmath>
    #include <vector>
    #define N 200000
    using namespace std;
    struct node
    {
    	int l,r,ll,rr,id,lca;
    }q[N+5];
    int n,m,a[N+5],st[N+5],ed[N+5],dfn[N+5],f[N+5],num,size[N+5],his[N+5],dep[N+5],son[N+5],top[N+5],c[N+5],tmp,blo,l=1,r,use[N+5],ans[N+5],data[N+5];
    vector <int> d[N+5];
    void dfs1(int u,int fa)  //树剖第一次深搜
    {
    	f[u]=fa;st[u]=++num;
    	size[u]=1;his[num]=u;
    	dep[u]=dep[fa]+1;
    	vector <int>::iterator it;
    	for (it=d[u].begin();it!=d[u].end();it++)
    	{
    		int v=(*it);
    		if (v==fa)continue;
    		dfs1(v,u);
    		size[u]+=size[v];
    		if (size[v]>size[son[u]])son[u]=v;
    	}
    	ed[u]=++num;his[num]=u;
    }
    void dfs2(int u,int to)   //树剖第二次深搜
    {
    	top[u]=to;
    	if (son[u])dfs2(son[u],to);
    	vector <int>::iterator it;
    	for (it=d[u].begin();it!=d[u].end();it++)
    	{
    		int v=(*it);
    		if (v!=son[u]&&v!=f[u])dfs2(v,v);
    	}
    }
    int Lca(int x,int y)   //树剖求lca
    {
    	while (top[x]!=top[y])
    	{
    		if (dep[top[x]]<dep[top[y]])swap(x,y);
    		x=f[top[x]];
    	}
    	if (dep[x]>dep[y])swap(x,y);
    	return x;
    }
    void add(int x)
    {
    	tmp+=(++c[a[x]]==1);
    }
    void del(int x)
    {
    	tmp-=(--c[a[x]]==0);
    }
    void calc(int x)     //对点进行加入或删除
    {
    	(!use[x])?add(x):del(x);
    	use[x]^=1;
    }
    int cmp(node x,node y)   //排序
    {
    	return (x.ll==y.ll)?(x.ll%2==1?x.r<y.r:x.r>y.r):x.l<y.l;
    }
    int main()
    {
    	scanf("%d%d",&n,&m);
    	for (int i=1;i<=n;i++)
    		scanf("%d",&a[i]),data[i]=a[i];
        sort(data+1,data+n+1);
        for(int i=1;i<=n;i++)a[i]=lower_bound(data+1,data+n+1,a[i])-data;  //离散化
    	int x,y;
    	for (int i=1;i<n;i++)
    	{
    		scanf("%d%d",&x,&y);
    		d[x].push_back(y);
    		d[y].push_back(x);
    	}
    	dfs1(1,0); 
    	dfs2(1,1);
    	blo=n*2/sqrt(m*2/3);
    	for (int i=1;i<=m;i++)
    	{
    		scanf("%d%d",&x,&y);
    		if (st[x]>st[y])swap(x,y);  //保证stx<sty
    		q[i].id=i;
    		q[i].lca=Lca(x,y);  
    		if (q[i].lca==x)    //x,y在以x为根的子树中
    		{
    			q[i].l=st[x];
    			q[i].r=st[y];
    			q[i].ll=st[x]/blo;
    			q[i].rr=st[y]/blo;
    			q[i].lca=0;
    		}
    		else
    		{
    			q[i].l=ed[x];
    			q[i].r=st[y];
    			q[i].ll=ed[x]/blo;
    			q[i].rr=st[y]/blo;
    		}
    	}
    	sort(q+1,q+m+1,cmp);
    	for (int i=1;i<=m;i++)
    	{
    		while (l>q[i].l)calc(his[--l]);
    		while (r<q[i].r)calc(his[++r]);
    		while (l<q[i].l)calc(his[l++]);
    		while (r>q[i].r)calc(his[r--]);
    		if (q[i].lca)calc(q[i].lca);
    		ans[q[i].id]=tmp;
    		if (q[i].lca)calc(q[i].lca);
    	}
    	for (int i=1;i<=m;i++)
    		printf("%d
    ",ans[i]);
    	return 0;
    }
    
  • 相关阅读:
    互联网秒杀设计
    生产者消费者模式实现
    Ping CAP CTO、Codis作者谈redis分布式解决方案和分布式KV存储
    VIM使用学习笔记 : 按键说明
    Cookie的有效访问路径
    简单的Cookie记录浏览记录案例
    认识Cookie和状态管理
    Java异常
    Java接口基础
    String常用方法
  • 原文地址:https://www.cnblogs.com/sdlang/p/13068119.html
Copyright © 2020-2023  润新知