今天我们简单的来看一下长在树上的莫队
要说莫队是一个很暴力,很高效的算法
考场上的暴力乱搞佳品
网上有国际友人的blog,不过需要一点英语知识(或者是google翻译)
最简单的不修改线性莫队
莫队重点就在于分块(这可是精髓)
一般的,我们就分成sqrt(n)块
struct node{
int x,y,block;
};
node Q[N];
int n;
int cmp(const node &a,const node &b)
{
if (a.block!=b.block) return a.block<b.block;
else return a.y<b.y; //右端点
}
void init()
{
scanf("%d",&n);
int unit=sqrt(n);
for (int i=1;i<=n;i++)
scanf("%d%d",&Q[i].x,&Q[i].y),
Q[i].block=(Q[i].x-1)/unit+1; //分块
sort(Q+1,Q+1+n,cmp);
}
我们看到这里block的计算比较玄妙(记住就好,记住就好,记住就好)
如果分块处理的不好,莫队算法的时间复杂度退化的很厉害(很容易就T掉了)
在处理询问的时候,我们设置两个指针
顺序处理询问,每次暴力维护答案
就以一道题为例:计算区间不同颜色数
int cnt[N];
void doit()
{
int L=1,R=0,num=0;
for (int i=1;i<=n;i++)
{
while (R<Q[i].y)
{
R++;
cnt[C[R]]++;
if (cnt[C[R]]==1) num++;
}
while (R>Q[i].y)
{
cnt[C[R]]--;
if (cnt[C[R]]==0) num--;
R--;
}
while (L<Q[i].x){
cnt[C[L]]--;
if (cnt[C[L]]==0) num--;
L++;
}
while (L>Q[i].x)
{
L--;
cnt[C[L]]++;
if (cnt[C[l]]==1) num++;
}
ans[Q[i].id]=num;
}
}
带修改的线性莫队
实际上思路和朴素的莫队一样
只不过增加了一个修改指针
这里不再冗述,可以去经典例题转一转
(代码挺好理解的)
子树树上莫队
树上莫队是莫队算法的拓展,思想依然差不多
朴素的莫队是在序列上进行的
能把树形结构与序列联系在一起的,最简单的就是dfs序
那么一个子树就对应dfs序上一段,所以我们就可以在dfs序上莫队
路径树上莫队
听说树上莫队只能搞子树询问?
然而外国友人用一个奇技淫巧把ta扩展到了路径询问
对树做一次深搜,第一次进入某节点时,将此节点编号加入序列,从某节点退出时,将此节点编号第二次加入序列
记录一个数在括号序中第一次出现(st)和最后一次出现的位置(ed)
我们假设要询问一条路径a-b,设p=lca(a,b)
不妨设st[a]<=st[b](否则交换一下)
- 当p=a时,这应该是一个比较简单的情形:a-b是一段父子链
我们考虑dfs序上[st[a],st[b]]的点,我们可以发现,a-b上的点被算了一遍,其他点都被算了2遍或0遍
这个时候我们就需要一个玄妙的操作消除影响:
void update(int x,int z)
{
int co=C[x];
num-=(bool)cnt[co]; //cnt是color的出现次数
cnt[co]-=vis[x]&1; //vis是结点在序列中出现的次数
vis[x]+=z;
cnt[co]+=vis[x]&1;
num+=(bool)cnt[co];
}
这是一种比较官方的写法,然而我又YY处理另一种写法(比较好理解)
void update(int x)
{
int co=C[x],z;
if (!vis[x]) vis[x]=1,z=1;
else vis[x]=0,z=-1;
if (cnt[co]==0&&z==1) num++;
cnt[co]+=z;
if (cnt[co]==0) num--;
}
- 当p≠a时,我们统计[ed[a],st[b]]的点(从ed[a]开始为保证a不会被排除掉),