莫队算法用于解决不带修改的离线区间问题。
其主要思想是这样的:
先已知区间[L,R]的答案为ans,如果求出区间[L+1,R],[L-1,R],[L,R-1],[L,R+1]的时间均为alpha(),那么就可以在alpha()的时间内转移一个单位区间,求出答案ans’。
因此,我们可以通过安排查询的顺序来使得转移次数尽量少。
可以用平面最小曼哈顿距离生成树来确定查询顺序,不过有一个更好写的方法是分块。
把区间分成sqrt(n)块,对于查询[l,r]和[l’,r’],如果l和l’在一个块内,那么就把r小的放前面。如果不在一个块内,那么把l小的前面。时间复杂度O(nsqrt(n))
然后查询的时候改变l和r,同时改变答案并保存。
最后按顺序输出答案。
贴一个NBUT 1457 Sona的莫队算法模板(求区间中每个数出现次数的立方和)
#include
#include
#include
#include
#define ll long long
using namespace std;
int pos[100010],col[100010],a[100010],cnt[100010],n,m,block;
ll cubesum;
struct node{
int l,r,id;
ll ans;
}data[100010];
bool cmp1(node a,node b)
{
if(pos[a.l]==pos[b.l])return a.rreturn a.lreturn a.idint pos,int val)
{
cubesum-=1ll*cnt[col[pos]]*cnt[col[pos]]*cnt[col[pos]];
cnt[col[pos]]+=val;
cubesum+=1ll*cnt[col[pos]]*cnt[col[pos]]*cnt[col[pos]];
}
int main()
{
while(~scanf("%d",&n))
{
cubesum=0;
memset(cnt,0,sizeof cnt);
block=sqrt(n);
for(int i=1;i<=n;++i)
scanf("%d",&a[i]),col[i]=a[i],pos[i]=(i-1)/block;
sort(a+1,a+n+1);
int len=unique(a+1,a+n+1)-a-1;
for(int i=1;i<=n;++i)
col[i]=lower_bound(a+1,a+len+1,col[i])-a;
scanf("%d",&m);
for(int i=1;i<=m;++i)
{
scanf("%d%d",&data[i].l,&data[i].r);
data[i].id=i;
}
sort(data+1,data+m+1,cmp1);
for(int i=1,l=1,r=0;i<=m;++i)
{
for(;r1,1);
for(;r>data[i].r;--r)update(r,-1);
for(;l1);
for(;l>data[i].l;--l)update(l-1,1);
data[i].ans=cubesum;
}
sort(data+1,data+m+1,cmp2);
for(int i=1;i<=m;++i)
printf("%I64d\n",data[i].ans);
}
}
接下来是树上莫队。这个是解决无修改离线树上路径的问题。
要思考怎么才能把树上离散的区间变成连续的。
常见的做法是dfs序。
即进u点记录一个时间戳为l,出u点也记录一个时间戳为r,
加入u比v先访问,u不是lca(u,v),则[u,v]就是r[u],l[v]。
如果u是lca(u,v),那么u,v在链上,则[u,v]就是l[u],l[v]。
然后还要注意在数列里面l,r是直接加减1的。
但是在树上不能这么做。
记s(u,v)为树上u到v路径上点的集合,那么s(u,v)=s(u,root) xor s(v,root) xor lca(u,v)
记t(u,v)=s(u,root) xor s(v,root)
则t(u,v’)=s(u,root) xor s(v’,root)
因此t(u,v) xor t(u,v’)=s(v,root) xor s(v’,root)=t(v,v’)
这样说明把区间[u,v]移到区间[u,v’]的时候应该对[v,v’]上除了lca(v,v’)外的所有点取反。
贴一个SPOJ COT2
#include
#include
#include
#include
#define ll long long
using namespace std;
struct node{
int v,next;
}edge[80010];
struct query{
int l,r,ans,id;
}data[100010];
int sz[40010],top[40010],son[40010],a[40010],col[40010],sum[40010],l[40010],r[40010],dfn[40010],dep[40010],fa[40010],head[40010],pos[80010],idx[80010],block,cnt,dcnt,tot,now,n,m,len;
bool vis[40010];
bool cmp1(query a,query b)
{
if(pos[a.l]==pos[b.l])return a.rreturn a.lbool cmp2(query a,query b)
{
return a.idvoid addedge(int u,int v)
{
edge[cnt].v=v;
edge[cnt].next=head[u];
head[u]=cnt++;
}
void dfs1(int u,int f)
{
sz[u]=1;
int maxch=0;
dfn[u]=++dcnt;
l[u]=++tot;
idx[tot]=u;
for(int i=head[u];~i;i=edge[i].next)
{
int v=edge[i].v;
if(v!=f)
{
fa[v]=u;
dep[v]=dep[u]+1;
dfs1(v,u);
sz[u]+=sz[v];
if(sz[v]>maxch)
{
maxch=sz[v];
son[u]=v;
}
}
}
r[u]=++tot;
idx[tot]=u;
}
void dfs2(int u,int t)
{
top[u]=t;
if(son[u]==0)return;
dfs2(son[u],t);
for(int i=head[u];~i;i=edge[i].next)
{
int v=edge[i].v;
if(v!=son[u]&&v!=fa[u])dfs2(v,v);
}
}
int lca(int u,int v)
{
while(top[u]!=top[v])
{
if(dep[top[u]]return dep[u]void update(int u)
{
if(vis[u])now-=((--sum[col[u]])==0);
else now+=((++sum[col[u]])==1);
vis[u]^=1;
}
int main()
{
memset(head,-1,sizeof head);
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i)
scanf("%d",&a[i]),col[i]=a[i];
sort(a+1,a+n+1);
len=unique(a+1,a+n+1)-a-1;
for(int i=1;i<=n;++i)
col[i]=lower_bound(a+1,a+len+1,col[i])-a;
for(int i=1,u,v;iscanf("%d%d",&u,&v);
addedge(u,v);
addedge(v,u);
}
fa[1]=0;
dep[1]=1;
dfs1(1,0);
dfs2(1,1);
block=sqrt(n);
for(int i=1;i<=tot;++i)pos[i]=(i-1)/block;
for(int i=1,x,y;i<=m;++i)
{
scanf("%d%d",&x,&y);
if(dfn[x]>dfn[y])swap(x,y);
int t=lca(x,y);
if(t==x)data[i].l=l[x],data[i].r=l[y];
else data[i].l=r[x],data[i].r=l[y];
data[i].id=i;
}
sort(data+1,data+m+1,cmp1);
for(int i=1,l=1,r=0;i<=m;++i)
{
while(lwhile(l>data[i].l)update(idx[--l]);
while(rwhile(r>data[i].r)update(idx[r--]);
int u=idx[l],v=idx[r],t=lca(u,v);
if(t!=u&&t!=v)update(t);
data[i].ans=now;
if(t!=u&&t!=v)update(t);
}
sort(data+1,data+m+1,cmp2);
for(int i=1;i<=m;++i)
printf("%d\n",data[i].ans);
}
最后再来一个带修改莫队,时间复杂度O(n^(5/3))
待修改的那么就把时间看成第三维,然后就变成了一个空间曼哈顿最小距离生成树,这个怎么求?= =
其实好像没人研究吧。
不过还是可以分块来做的。
每个块的大小是n^(2/3),分成n^(1/3)块
然后排序莫队。
注意是每次询问操作时间+1,每次修改操作不加时间。
这个代码是给定一个N个正整数的序列S,编号0~N-1。给出M个操作,每个操作可以是以下两种之一:
(1)Q x y: 询问区间S[x]..S[y-1]中有多少种整数,相同的整数算一种
(2)M x y:表示 S[x] = y
正解是带修改可持久化线段树,要先离散化,再转换一下数组,转化成前一个数出现的位置。O(log^2n)
或者直接分块(不是莫队),保存前一个数出现的位置。O(nsqrt(n))
莫队能得90分,最后一个T了。
1s有点短。。1.5s可以AC。
#include
#include
#include
#include
#define ll long long
using namespace std;
int pos[50010],his[50010],ans[50010],col[50010],cnt[1000010],n,m,block,szq,szm,sum;
char op[3],c;
bool vis[1000010];
struct node{
int l,r,id,t;//in query l,r means [L,R],t means time,id=t is used to find where the answer is.
}query[50010],modification[50010];//in modification l means from,r means to,id means where will be modified,t means time
inline void GET(int &n)
{
n=0;
do{c=getchar();}while(c >'9'||c<'0');
while(c>='0'&&c<='9'){n=n*10+c-'0';c=getchar();}
}
inline bool cmp1(node a,node b)
{
if(pos[a.l]==pos[b.l])
{
if(pos[a.r]==pos[b.r])
return a.treturn a.rreturn a.linline void update(int u)
{
if(vis[u])sum-=((--cnt[col[u]])==0);
else sum+=((++cnt[col[u]])==1);
vis[u]^=1;
}
inline void change(node x,bool f)
{
if(f)swap(x.l,x.r);
col[x.id]=x.r;
if(vis[x.id])
{
sum-=((--cnt[x.l])==0);
sum+=((++cnt[x.r])==1);
}
}
int main()
{
GET(n),GET(m);
block=pow(n,2.11/3);
for(int i=1;i<=n;++i)
GET(col[i]),pos[i]=(i-1)/block;
for(int i=1,x,y;i<=m;++i)
{
scanf("%s",op);
GET(x),GET(y);
++x;
if(op[0]=='M')
{
modification[++szm].id=x;
modification[szm].l=col[x];
modification[szm].r=col[x]=y;
modification[szm].t=szq;
}
else
{
query[++szq].l=x;
query[szq].r=y;
query[szq].t=szq;
}
}
sort(query+1,query+szq+1,cmp1);
for(int i=1,l=1,r=0,t=szm;i<=szq;++i)
{
for(;r1);
for(;r>query[i].r;--r)update(r);
for(;lfor(;l>query[i].l;--l)update(l-1);
for(;t>0&&modification[t].t>=query[i].t;--t)
change(modification[t],1);
for(;t1].t1],0);
ans[query[i].t]=sum;
}
for(int i=1;i<=szq;++i)
printf("%d\n",ans[i]);
}