UVA11998 瓦里奥世界 Rujia Liu loves Wario Land! ——树链剖分启发式合并
-1前言:(废话)
在这里感谢@_the_Despair_大佬,他为我提供了比块状链表更好实现也更快的分块的想法。
借鉴了紫书(《算法竞赛入门经典》)的一部分思路,并向大家推荐这本书以及它的训练指南。
我在UVA上速度是世界第一!(虽然很快就会被大佬碾压)
(以及我在洛谷上交了30多遍才过的丢人战绩)
强烈建议大家读完题目分析以及调试助手就去实现自己的代码,会对代码能力有很大的提升。
0、前置芝士:
基本的分块,理解原理即可
1.题目内容
题目大意:
一开始有互不连通的n个点,每个点有点权,然后支持三种操作:
1 x y 从x到y连边,如果x与y已经联通,就忽略此命令
2 x v 把节点x的权值改为v
3 x y v统计从x到y的路径上权值小于等于v的点的数量以及这些点权值的乘积模k(是一个给定的数),如果x与y不连通或者没有满足条件的点,输出0(注意是一个0,而不是"0 0“或者"0 1")
输入格式:
本题强制在线且有多组数据。
我们令本组数据中之前输出的最后一个数为lastans(如果在这组数据中之前没有输出过任何数,lastans=0)
对于每组数据,输入为:
第一行:n,m,k,n(n<=50000)为点数,m(m<=100000)为操作数,k是上面说的那个模数(2<=k<=33333)
第二行:n个数,第i个数代表i号点的权值。
接下来m行,每行第一个数为opt,代表操作的编号,则输入为三种:
1 x+lastans y+lastans
2 x+lastans v+lastans
3 x+lastans y+lastans v+lastans,实际的输入需要减去lastans。
输出格式:
每一行输出对于每个3操作得到的答案。
2题目分析
树链剖分启发式合并模板题(其实我就见到这一道)
注意本题强制在线,所以我们只有三种选择,即为动态树、树链剖分和欧拉序列。
由于操作3的特殊性,欧拉序列必须要满足区间减法,所以不予考虑(我也不会)
动态树维护的话需要在O(1)的时间里根据子树的信息计算这个节点的信息,然而操作3的信息极为复杂,需要块状链表、分块或树套树才能维护,所以也做不到。
只能从树剖入手,我们对于三个操作倒着分析。
操作3:
我们用分块维护信息,块的大小最优为50-300,个人推荐150,对于每条重链,我们把它分成好几块去维护信息,代替已经无法发挥作用的线段树。
我们还需要维护块内前缀积,即定义pre[i]为块内前i小的点的权值的乘积,所以我们也需要将块内部的元素按着点权排序。
于是我们可以对于每个节点开一个动态数组,记录lt[u]为u所在的块中深度最小的节点,那我们就把u的信息记录在lt[u]的动态数组中。
那我们如何查询重链的信息呢?
我们假设要查x到top[x]这条重链上的信息,对于x所在的块,我们直接在树上向上一直跳到lt[u],把包括lt[u]的答案暴力统计。
之后就是整块了,整块怎么做?
注意到我们是查找下标最大的点权<=v的点,于是可以直接二分,取pre值即可。
以上是处理重链,之后直接套树剖查询的模板即可,那又如何处理top[x]==top[y]但lt[x]!=lt[y]的情况呢,
注意到之前在查询重链也有类似的情况,我们直接暴力统计不整块,二分整块即可。
最后是lt[x]==lt[y]&&x!=y的情况,我们直接暴力把深度大的节点跳到深度小的节点即可。
注意别忘了跳到一起后算上那一个点的答案。
从题目里看出我们还需要判断连通性,我们直接暴力跳重链跳到根,只要根是一样的,两点就联通。
操作2:
这个不难,我们记录一下块内节点的编号,直接遍历暴力修改即可,别忘了重做一遍这个块的预处理。
操作1
先找根判连通性
这是最复杂的,之前这种东西需要动态树维护,但是这次我们选用了树链剖分,该怎么办呢?
注意到只有连边没有删边,于是我们可以暴力启发式合并,把小树合并到大树来,
那关键也就在于如何快速合并树剖
先重建小树的树剖
对于处理重链有两种情况,如图:

第二种情况直接建以y为根的树剖即可,那关键就在于第一种情况。
我们重建x的重儿子的子树的树剖,然后重建x所在的块,之后我们对于y,顺着x的块向下建以y为根的树剖,
别忘了把x与y连边。
之后还有一项没处理:size值(启发式合并要用)
我们可以在向上的每一块上打个tag,判断时加上即可。
对于最底下的不整块,还是暴力加上就可以。
在最后,重做一遍x所在的块的预处理,(还是假设把y合并到x上),顺带说一句dfs2的时候(也就是处理重链的时候)要顺便把预处理做了,单独预处理会较为复杂而且慢。
3.调试助手
常见错误:
1.数组开小,这题很奇怪,我一开始就被这坑苦了(二十多次评测都因为这个),数组必须开的很大,不开大会T,建议跟点与边有关的要开1e6。
2.多组数据一定要记得清空!
3.在dfs第一遍(dfs1)的时候一定要清空重儿子,不然如果是重建树剖换根后原本不是叶子节点的点变成叶子了会向回跑,或者原本的儿子的子树更大也会影响答案。
4.向上跳的时候漏写了向上跳的操作。
5.忘记判断连通性。
6.找根找错了。
对拍用具提取码: 7xvb
这里边是我的标程的exe,以及离线数据的生成器,对拍可以用。
标程从Wario.txt读入,输出到ans.txt。
标程读入的是离线数据(也就是去掉lastans后的)
建议各位读到这就去实现自己的代码
4 code
#include <bits/stdc++.h>
using namespace std;
const int MAXN=2000001;
const int MAXM=2000001;
const int MAXB=150;
struct Edge
{
int from,to,nxt;
}e[MAXM<<1];
int head[MAXN],kcnt;
void add(int f,int t)
{
e[++kcnt].from=f;
e[kcnt].to=t;
e[kcnt].nxt=head[f];
head[f]=kcnt;
return ;
}
int top[MAXN],lt[MAXN],siz[MAXN],fa[MAXN],depth[MAXN],son[MAXN],w[MAXN];
int n,m,mod,ans1,ans2,lastans;
struct Node
{
int sum,num,val;//sum是前缀积,num是这个点的编号,val是这个点的点权
bool operator<(const Node& b) const
{
return this->val<b.val;
}
Node(int v,int nu):val(v),num(nu){}
};
struct info
{
int tag;//这就是那个size的懒标记
vector<Node>bl;
void pre(void)//重建块
{
int len=this->bl.size();
if(!len)
return ;
sort(this->bl.begin(),this->bl.end());
bl[0].sum=bl[0].val;
for(int i=1;i<len;i++)
bl[i].sum=bl[i-1].sum*bl[i].val%mod;
}
int getcnt(int v)//块内二分
{
int l=0,r=bl.size()-1,ret=-1;
while(l<=r)
{
int mid=(l+r)>>1;
if(bl[mid].val<=v)
l=mid+1,ret=mid;
else
r=mid-1;
}
return ret;
}
}info[MAXN];
int find(int x)//找根
{
while(fa[x])
x=top[fa[x]];
return x;
}
void dfs1(int u,int f)//清空+预处理基本信息
{
depth[u]=depth[f]+1;
info[u].bl.clear();
son[u]=0;
top[u]=lt[u]=u;
fa[u]=f;
siz[u]=1;
info[u].tag=0;
for(int i=head[u];i;i=e[i].nxt)
{
int v=e[i].to;
if(v==f)
continue;
dfs1(v,u);
siz[u]+=siz[v];
if(siz[v]>siz[son[u]])
son[u]=v;
}
return ;
}
void dfs2(int u,int tp)//预处理重链信息+块内信息
{
top[u]=tp;
if(u==tp)
{
lt[u]=u;
info[u].bl.push_back(Node(w[u],u));
}
else
if(info[lt[fa[u]]].bl.size()<MAXB)
{
lt[u]=lt[fa[u]];
info[lt[u]].bl.push_back(Node(w[u],u));
}
else
{
lt[u]=u;
info[u].bl.push_back(Node(w[u],u));
}
if(son[u])
dfs2(son[u],tp);
for(int i=head[u];i;i=e[i].nxt)
{
int v=e[i].to;
if(v==fa[u]||v==son[u])
continue;
dfs2(v,v);
}
if(lt[u]==u)
info[u].pre();
return ;
}
void link(int x,int y)//连接x与y
{
int rx=find(x),ry=find(y);
if(rx==ry)
return ;
if(siz[rx]+info[rx].tag<siz[ry]+info[ry].tag)
{
swap(rx,ry);
swap(x,y);
}
if(siz[son[x]]<siz[ry]+info[lt[ry]].tag)
{
int s=son[x];
son[x]=y;
if(s)
{
info[lt[x]].bl.clear();
dfs1(s,x);
dfs2(s,s);
s=fa[s];
while(s!=lt[x])
{
info[lt[x]].bl.push_back(Node(w[s],s));
s=fa[s];
}
info[lt[x]].bl.push_back(Node(w[lt[x]],lt[x]));
}
}
add(x,y);
add(y,x);
fa[y]=x;
dfs1(y,x);
dfs2(y,y==son[x]?top[x]:y);
int Now=x;
while(Now!=lt[x])
{
siz[Now]+=siz[y];
Now=fa[Now];
}
siz[lt[x]]+=siz[y];
Now=fa[Now];
while(lt[Now]!=rx&&Now)
{
info[lt[Now]].tag+=siz[y];
Now=fa[lt[Now]];
}
if(Now)
info[lt[Now]].tag+=siz[y];
info[lt[x]].pre();
return ;
}
void change(int x,int v)//暴力修改即可
{
v%=mod;
int len=info[lt[x]].bl.size();
w[x]=v;
for(int i=0;i<len;i++)
if(info[lt[x]].bl[i].num==x)
{
info[lt[x]].bl[i].val=v;
break;
}
info[lt[x]].pre();
return ;
}
void query(int x,int v)//查询x->top[x]的信息,特判较多,但我觉得思路清晰
{
int Now=x;
while(Now!=lt[x])
{
if(w[Now]<=v)
ans1++,ans2=ans2*w[Now]%mod;
Now=fa[Now];
}
if(w[lt[x]]<=v)
ans1++,ans2=ans2*w[Now]%mod;
if(top[x]==lt[x])
return ;
Now=fa[lt[x]];
while(lt[Now]!=top[x])
{
int ans=info[lt[Now]].getcnt(v);
if(ans==-1)
{
Now=fa[lt[Now]];
continue;
}
ans1+=ans+1;
ans2=ans2*info[lt[Now]].bl[ans].sum%mod;
Now=fa[lt[Now]];
}
int ans=info[top[x]].getcnt(v);
if(ans==-1)
return ;
ans1+=ans+1;
ans2=ans2*info[top[x]].bl[ans].sum%mod;
return ;
}
void getans(int x,int y,int v)//就是三阶段:1.跳重链2.处理lt[x]!=lt[y]3.在一个块内处理x与y之间的信息
{
if(find(x)!=find(y))
return ;
while(top[x]!=top[y])
{
if(depth[top[x]]<depth[top[y]])
swap(x,y);
query(x,v);
x=fa[top[x]];
}
if(depth[x]<depth[y])
swap(x,y);
int Now=x;
while(lt[Now]!=lt[y])
{
if(lt[son[Now]]==lt[Now])
{
if(w[Now]<=v)
ans1++,ans2=ans2*w[Now]%mod;
Now=fa[Now];
continue;
}
Now=lt[Now];
int ans=info[Now].getcnt(v);
if(ans==-1)
{
Now=fa[Now];
continue;
}
ans1+=(ans+1);
ans2=ans2*info[Now].bl[ans].sum%mod;
Now=fa[Now];
}
while(Now!=y)
{
if(w[Now]<=v)
ans1++,ans2=ans2*w[Now]%mod;
Now=fa[Now];
}
if(w[Now]<=v)
ans1++,ans2=ans2*w[Now]%mod;
return ;
}
int read(void)
{
int x=0,f=1;
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;
}
void write(long long x)
{
if (x < 0) x = ~x + 1, putchar('-');
if (x > 9) write(x / 10);
putchar(x % 10 + '0');
}
void init(int x)//多组数据一定要清空!!!切记
{
son[x]=head[x]=0;
lt[x]=top[x]=x;
info[x].tag=0;
siz[x]=1;
fa[x]=0;
depth[x]=1;
info[x].bl.clear();
return ;
}
int main(void)
{
while(scanf("%d %d %d",&n,&m,&mod)!=EOF)
{
kcnt=ans1=ans2=lastans=depth[0]=0;
memset(fa,0,sizeof(fa));
for(int i=1;i<=n;i++)
{
init(i);
scanf("%d",&w[i]);
dfs1(i,0);
dfs2(i,i);
}
while(m--)
{
int opt=read(),x=read(),y=read();
if(opt==1)
link(x-lastans,y-lastans);
else if(opt==2)
change(x-lastans,y-lastans);
else if(opt==3)
{
int v=read();
ans1=0,ans2=1;
getans(x-lastans,y-lastans,v-lastans);
lastans=(!ans1)?0:ans2;
write(ans1);
if(ans1)
{
putchar(' ');
write(ans2);
}
putchar('\n');
}
}
}
return 0;
}

浙公网安备 33010602011771号