给出一个长度为N序列(为什么D、E、F都是序列)
需要支持两种操作:
1、定义一个区间的值为:这段区间任意元素出现次数的集合的mex,给出l,r求原串中[l,r]这段区间的值
2、修改某个点的值
对mex的定义与SG函数中是相同的,表示一个自然数集中未出现的最小的整数。
例如: 1 、 3 、 2 、 1 、 2 、 2 、 2 1、3、2、1、2、2、2 1、3、2、1、2、2、2这个序列的值为3:
3出现了1次,1出现了2次,2出现了4次,集合中的数为 0 , 1 , 2 , 4 0,1,2,4 0,1,2,4(所有除了这三个数以外的 [ 0 , 1 0 9 ] [0,10^9] [0,109]的数均未出现,所以为0)
N , M ( 操 作 次 数 ) ≤ 1 0 5 N,M(操作次数)≤10^5 N,M(操作次数)≤105
时限:4s
很直观的带修改的莫队算法题(我居然没学过就猜中了)
因为只学过普通的莫队算法,而且还是16年准备省选的时候学的,早就忘的差不多了,于是又回去补了补,现在重新写一个总结,以免以后再忘。
莫队算法被称为区间问题的神器,但这神器的限制其实也颇多:必须离线操作,不支持区间修改,不支持无法在 O ( 1 ) O(1) O(1)或近似的复杂度内,将区间 [ l , r ] [l,r] [l,r]的答案通过加入一个值/删去一个值以得到 [ l − 1 , r ] [l-1,r] [l−1,r], [ l , r − 1 ] [l,r-1] [l,r−1], [ l + 1 , r ] [l+1,r] [l+1,r], [ l , r + 1 ] [l,r+1] [l,r+1]的答案的算法。并且,其复杂度为 ( N + M ) ∗ N (N+M)*\sqrt N (N+M)∗N,所以当 N ≥ 1 0 6 N≥10^6 N≥106时,莫队算法就有TLE的可能。
说了那么多缺点,那么接下来就该大吹特吹了:
其优点是代码简单,考场上实用性强,是许多区间问题中bug级的存在,令出题人焦头烂额(谁也不想自己琢磨了半天搞出来的区间题,被人用莫队水过去)
先说说不带修改的莫队算法:
首先,莫队算法是基于分块进行的,将原串拆分为 N \sqrt N N个块,每个块的大小自然也是 N \sqrt N N级的,现在我们将所有询问,按照其左端点所在的块排序,左端在相同块中的,按照右端点排序。
假设当前已经得到了 [ L i , R i ] [L_i,R_i] [Li,Ri]的答案,我们通过依次插入和删除的方式,来得到 [ L i + 1 , R i + 1 ] [L_{i+1},R_{i+1}] [Li+1,Ri+1]
这看似十分暴力的算法,实则是有严格的复杂度证明的。
我们从 L L L与 R R R的偏移量来计算其复杂度:
首先是 L L L的偏移量:因为我们总共需要处理M个询问,每两个相邻的询问之间,若 L L L在同一个块中,那么其偏移量为 N \sqrt N N,若不在同一个块中,其偏移量为 2 N 2\sqrt N 2N,所以 L L L的复杂度为 M N M\sqrt N MN
然后是 R R R的偏移量:其实只要稍加分析就会发现,对于同一个块中, R R R是升序排列的,所以每个块的 R R R的偏移量均为 N N N,总计 N \sqrt N N个块,所以 R R R的偏移量为 N N N\sqrt N NN
所以,莫队算法的复杂度就可以得到了: ( M + N ) N (M+N)\sqrt N (M+N)N
模板题:BZOJ2038小Z的袜子
#include
#include
#include
#include
#define SF scanf
#define PF printf
#define MAXN 50010
using namespace std;
int s[MAXN],n,m;
long long ans;
int b[MAXN];
int pos[MAXN];
struct node{
int id;
long long a,b,l,r;
}a[MAXN];
long long gcd(long long x,long long y){
if(x==0)
return y;
return gcd(y%x,x);
}
bool cmp(node x,node y){
return pos[x.l]<pos[y.l]||(pos[x.l]==pos[y.l]&&x.r<y.r);
}
bool sort_by_id(node x,node y){
return x.id<y.id;
}
void init(){
SF("%d%d",&n,&m);
int block=sqrt(n);
for(int i=0;i<n;i++){
SF("%d",&b[i]);
pos[i]=i/block;
}
for(int i=0;i<m;i++){
SF("%d%d",&a[i].l,&a[i].r);
a[i].id=i;
}
}
void update(int x,int r){
ans-=s[b[x]]*s[b[x]];
s[b[x]]+=r;
ans+=s[b[x]]*s[b[x]];
}
void work(){
for(int i=0,l=0,r=-1;i<m;i++){
if(a[i].l==a[i].r){
a[i].b=1;
continue;
}
for(;r>a[i].r-1;r--)
update(r,-1);
for(;r<a[i].r-1;r++)
update(r+1,1);
for(;l>a[i].l-1;l--)
update(l-1,1);
for(;l<a[i].l-1;l++)
update(l,-1);
a[i].a=ans-(a[i].r-a[i].l+1);
a[i].b=(a[i].r-a[i].l+1)*(a[i].r-a[i].l);//PF("(%d %I64d %I64d %d)",ans,a[i].a,a[i].b,i);
long long k=gcd(a[i].a,a[i].b);
//PF("{%d}\n",k);
a[i].a/=k;
a[i].b/=k;
}
}
int main(){
init();
sort(a,a+m,cmp);
work();
sort(a,a+m,sort_by_id);
for(int i=0;i<m;i++)
PF("%lld/%lld\n",a[i].a,a[i].b);
}
其实带修改的莫队算法本质并没有改变:
唯一的区别在于:我们分的块的大小不能是 N 0.5 N^{0.5} N0.5了,每个块的大小均为 N 2 3 N^{\frac 2 3} N32
这样一来,总的块的数量为 N 1 3 N^{\frac 1 3} N31
设 T i T_i Ti为该询问时已经做的修改次数
我们只需要以 L L L所在的块为最优先, R R R所在的块为第二优先, T T T为第三优先来将询问排序,在依次插入和删除新元素时,同时也处理 T T T(同样是依次修改)即可:
while(l>q[i].l) Add(a[--l]);
while(r<q[i].r) Add(a[++r]);
while(l<q[i].l) Delet(a[l++]);
while(r>q[i].r) Delet(a[r--]);
while(now<q[i].x) work(++now,i);
while(now>q[i].x) dework(now--,i);
时间复杂度的证明与不修改是类似的。
对于 L L L与 R R R的偏移量,是与询问次数M是相关的,每两次相邻询问之间, L L L的偏移量在 N 2 3 N^{\frac 2 3} N32数量级, R R R有 N 1 3 N^{\frac 1 3} N31次询问有可能会偏移 N N N,其余的询问偏移量同样也是 N 2 3 N^{\frac 2 3} N32数量级的。所以 L L L与 R R R的复杂度均为 O ( N 5 3 ) O(N^{\frac 5 3}) O(N35)(假设M与N为同一数量级)
对于 T T T的偏移量,与不修改中的 R R R偏移量类似,同样是:前两个维度所有可能情况*N
刚才已经说明了,块的数量为 N 1 3 N^{\frac 1 3} N31个,所以 L , R L,R L,R分别有 N 1 3 N^{\frac 1 3} N31种情况,其总的可能情况根据乘法原理为 N 2 3 N^{\frac 2 3} N32种,所以 T T T的复杂度同样也为 O ( N 5 3 ) O(N^{\frac 5 3}) O(N35)
提供一个小技巧:因为计算机算 N 2 3 N^{\frac 2 3} N32不方便计算,所以通常我们都是手算出 N N N的最大情况下的 N 2 3 N^{\frac 2 3} N32,将其作为一个常数直接赋值给块的大小即可。
模板题CF940F(可能这道题当模板有些不友好,所以附了题解)
题解:其实就是一个小技巧,我们可以将序列中的数字离散化,这里的离散化很简单,因为不用考虑大小关系,只需考虑是否相同即可,所以用一个map存储就能实现。
离散化之后,所有可能出现的值的个数就缩小到了 1 0 5 10^5 105级别,那么我们可以用一个数组存储每个数出现的次数即可,再用一个数组存储每个数出现次数的出现次数(有点抽象,看看代码可能有帮助)。
很容易发现,我们的答案一定是小于1000,所以我们暴力算这个值都没问题。
#include
#include
#include
#include
#include
#define SF scanf
#define PF printf
#define MAXN 200010
using namespace std;
int n,m,b,qnum,cnum;
struct node{
int l,r,x,id;
}q[MAXN];
struct change{
int pos,val,rval;
}c[MAXN];
int col[MAXN],res,blo[MAXN],a[MAXN],a1[MAXN],maxi;
int ans1[MAXN],ans[MAXN],ans2[MAXN];
map<int,int> mp;
int cmp(node a,node b){
if(blo[a.l]!=blo[b.l])
return blo[a.l]<blo[b.l];
if(blo[a.r]!=blo[b.r])
return blo[a.r]<blo[b.r];
return a.x<b.x;
}
void Add(int val){
if(ans1[val]>0)
ans2[ans1[val]]--;
ans1[val]++;
ans2[ans1[val]]++;
}
void Delet(int val){
ans2[ans1[val]]--;
ans1[val]--;
if(ans1[val]>0)
ans2[ans1[val]]++;
}
void dework(int now,int i){
if(c[now].pos>=q[i].l&&c[now].pos<=q[i].r){
Delet(c[now].val);
Add(c[now].rval);
}
a[c[now].pos]=c[now].rval;
}
void work(int now,int i){
if(c[now].pos>=q[i].l&&c[now].pos<=q[i].r){
Delet(c[now].rval);
Add(c[now].val);
}
a[c[now].pos]=c[now].val;
}
void Moqueue(){
int l=1,r=1,now=0;
Add(a[1]);
int i;
for(i=1;i<=qnum;i++){
while(l>q[i].l)
Add(a[--l]);
while(r<q[i].r)
Add(a[++r]);
while(l<q[i].l)
Delet(a[l++]);
while(r>q[i].r)
Delet(a[r--]);
while(now<q[i].x)
work(++now,i);
while(now>q[i].x)
dework(now--,i);
for(int j=1;;j++)
if(ans2[j]==0){
ans[q[i].id]=j;
break;
}
}
for(int i=1;i<=qnum;i++)
PF("%d\n",ans[i]);
}
int main(){
SF("%d%d",&n,&m);
b=2000;
int cnt=0;
for(int i=1;i<=n;i++){
SF("%d",&a[i]);
if(mp[a[i]]==0)
mp[a[i]]=++cnt;
a[i]=mp[a[i]];
a1[i]=a[i];
blo[i]=(i-1)/b+1;
}
int tag,x,y;
for(int i=1;i<=m;i++){
SF("%d%d%d",&tag,&x,&y);
if(tag==1){
q[++qnum].l=x;
q[qnum].r=y;
q[qnum].x=cnum;
q[qnum].id=qnum;
}
else{
c[++cnum].pos=x;
if(mp[y]==0)
mp[y]=++cnt;
y=mp[y];
c[cnum].val=y;
c[cnum].rval=a1[x];
a1[x]=y;
}
}
sort(q+1,q+1+qnum,cmp);
Moqueue();
}
//这年头,什么都流行上树,像什么母猪,HJB上树都不新鲜了,所以我们的莫队算法也要上树
其实树上莫队只是借助了DFS序列(括号序列,每个元素出现两次,设靠前的一次为 f i r [ x ] fir[x] fir[x],靠后的一次为 l a s [ i ] las[i] las[i])而已
总所周知,DFN是个神奇的工具,能够将树形结构压缩成一个一维的数组。
莫队算法作为区间问题的神器,不能直接在树上搞,所以就只能在DFN上进行操作
比较简单的是询问子树信息(DFN序),对于每次询问的点x,我们更改为:
L = f i r [ x ] , R = l a s [ i ] L=fir[x],R=las[i] L=fir[x],R=las[i]
所以我们就将问题转化为求DFN中 [ L , R ] [L,R] [L,R]这个区间了
比较恶心一点的是求路径信息(括号序列),对于每次询问的两个点 ( u , v ) (u,v) (u,v):
如果一点是另一点的祖先(设:u为v的祖先)
L = l a s [ v ] , R = l a s [ u ] L=las[v],R=las[u] L=las[v],R=las[u]
如果两点之间没有直接的祖先关系(设u在DFN序列中比v更靠前出现):
L = l a s [ u ] , R = f i r [ v ] L=las[u],R=fir[v] L=las[u],R=fir[v]
但这种情况很容易发现它们的lca没有出现在这个区间内,所以再加入lca即可。
当然还有带修改的树上莫队(笑),其做法就是:树上莫队+带修改莫队即可
不带修改模板题:COT2
#include
#include
#include
#include
#include
#include
#define SF scanf
#define PF printf
#define MAXN 100010
using namespace std;
vector<int> a[MAXN];
int fir[MAXN],bac[MAXN],col[MAXN];
int blo[MAXN],dfn[MAXN],cnt,sum[MAXN];
int fa[MAXN][20],deep[MAXN],ansx[MAXN],n,m,ans;
bool used[MAXN];
struct node{
int l,r,id;
bool p;
}q[MAXN];
map<int,int> mp;
void dfs(int x){
dfn[++cnt]=x;
fir[x]=cnt;
deep[x]=deep[fa[x][0]]+1;
for(int i=1;i<20;i++)
fa[x][i]=fa[fa[x][i-1]][i-1];
for(int i=0;i<a[x].size();i++){
if(a[x][i]==fa[x][0])
continue;
fa[a[x][i]][0]=x;
dfs(a[x][i]);
}
dfn[++cnt]=x;
bac[x]=cnt;
}
int lca(int x,int y){
if(deep[x]<deep[y])
swap(x,y);
for(int i=19;i>=0;i--)
if(deep[fa[x][i]]>=deep[y])
x=fa[x][i];
if(x==y)
return x;
for(int i=19;i>=0;i--)
if(fa[x][i]!=fa[y][i]){
x=fa[x][i];
y=fa[y][i];
}
return fa[x][0];
}
bool cmp(node a,node b){
if(blo[a.l]!=blo[b.l])
return blo[a.l]<blo[b.l];
return a.r<b.r;
}
void Add(int x){
if(used[x]==1){
sum[col[x]]--;
if(sum[col[x]]==0)
ans--;
used[x]=0;
}
else{
sum[col[x]]++;
if(sum[col[x]]==1)
ans++;
used[x]=1;
}
}
void Moqueue(){
int l=1,r=1;
Add(dfn[1]);
for(int i=1;i<=m;i++){
while(l>q[i].l)
Add(dfn[--l]);
while(l<q[i].l)
Add(dfn[l++]);
while(r<q[i].r)
Add(dfn[++r]);
while(r>q[i].r)
Add(dfn[r--]);
int lcax=lca(dfn[l],dfn[r]);
if(q[i].p)
Add(lcax);
ansx[q[i].id]=ans;
if(q[i].p)
Add(lcax);
}
for(int i=1;i<=m;i++)
PF("%d\n",ansx[i]);
}
int main(){
//freopen("data.in","r",stdin);
//freopen("data.out","w",stdout);
int x,y;
SF("%d%d",&n,&m);
for(int i=1;i<=n;i++){
SF("%d",&col[i]);
if(mp[col[i]]==0)
mp[col[i]]=++cnt;
col[i]=mp[col[i]];
}
for(int i=1;i<n;i++){
SF("%d%d",&x,&y);
a[x].push_back(y);
a[y].push_back(x);
}
dfs(1);
int b=sqrt(cnt);
for(int i=1;i<=cnt;i++)
blo[i]=i/b+1;
for(int i=1;i<=m;i++){
SF("%d%d",&q[i].l,&q[i].r);
if(fir[q[i].l]>fir[q[i].r])
swap(q[i].l,q[i].r);
if(bac[q[i].l]>fir[q[i].r]){
x=bac[q[i].r];
y=bac[q[i].l];
q[i].p=0;
}
else{
x=bac[q[i].l];
y=fir[q[i].r];
q[i].p=1;
}
q[i].l=x;
q[i].r=y;
q[i].id=i;
}
sort(q+1,q+1+m,cmp);
Moqueue();
}
带修改模板题:糖果公园
#include
#include
#include
#include
#include
#include
#define SF scanf
#define PF printf
#define MAXN 200010
using namespace std;
vector<int> a[MAXN];
int fir[MAXN],bac[MAXN],col[MAXN],col1[MAXN];
int blo[MAXN],dfn[MAXN],cnt;
int fa[MAXN][20],deep[MAXN],n,cols,m,cntc,cntq;
bool used[MAXN];
long long ans,w[MAXN],v[MAXN],sum[MAXN],ansx[MAXN];
struct node{
int l,r,id,cntc;
bool p;
}q[MAXN];
struct chan{
int pos,val,rval;
}p[MAXN];
void dfs(int x){
dfn[++cnt]=x;
fir[x]=cnt;
deep[x]=deep[fa[x][0]]+1;
for(int i=1;i<20;i++)
fa[x][i]=fa[fa[x][i-1]][i-1];
for(int i=0;i<a[x].size();i++){
if(a[x][i]==fa[x][0])
continue;
fa[a[x][i]][0]=x;
dfs(a[x][i]);
}
dfn[++cnt]=x;
bac[x]=cnt;
}
int lca(int x,int y){
if(deep[x]<deep[y])
swap(x,y);
for(int i=19;i>=0;i--)
if(deep[fa[x][i]]>=deep[y])
x=fa[x][i];
if(x==y)
return x;
for(int i=19;i>=0;i--)
if(fa[x][i]!=fa[y][i]){
x=fa[x][i];
y=fa[y][i];
}
return fa[x][0];
}
bool cmp(node a,node b){
if(blo[a.l]!=blo[b.l])
return blo[a.l]<blo[b.l];
if(blo[a.r]!=blo[b.r])
return blo[a.r]<blo[b.r];
return a.cntc<b.cntc;
}
void Add(int x){
if(used[x]==1){
ans-=w[sum[col[x]]]*v[col[x]];
sum[col[x]]--;
used[x]=0;
}
else{
sum[col[x]]++;
ans+=w[sum[col[x]]]*v[col[x]];
used[x]=1;
}
}
void work(int i,int j){
if(used[p[i].pos]==1){
Add(p[i].pos);
col[p[i].pos]=p[i].val;
Add(p[i].pos);
}
col[p[i].pos]=p[i].val;
}
void dework(int i,int j){
if(used[p[i].pos]==1){
Add(p[i].pos);
col[p[i].pos]=p[i].rval;
Add(p[i].pos);
}
col[p[i].pos]=p[i].rval;
}
void Moqueue(){
int l=1,r=1,now=0;
Add(dfn[1]);
for(int i=1;i<=cntq;i++){
while(l>q[i].l)
Add(dfn[--l]);
while(l<q[i].l)
Add(dfn[l++]);
while(r<q[i].r)
Add(dfn[++r]);
while(r>q[i].r)
Add(dfn[r--]);
while(now<q[i].cntc)
work(++now,i);
while(now>q[i].cntc)
dework(now--,i);
int lcax=lca(dfn[l],dfn[r]);
if(q[i].p)
Add(lcax);
ansx[q[i].id]=ans;
if(q[i].p)
Add(lcax);
}
for(int i=1;i<=cntq;i++)
PF("%lld\n",ansx[i]);
}
int main(){
//freopen("data.in","r",stdin);
//freopen("data.out","w",stdout);
int x,y;
SF("%d%d%d",&n,&cols,&m);
for(int i=1;i<=cols;i++)
SF("%d",&v[i]);
for(int i=1;i<=n;i++)
SF("%d",&w[i]);
for(int i=1;i<n;i++){
SF("%d%d",&x,&y);
a[x].push_back(y);
a[y].push_back(x);
}
for(int i=1;i<=n;i++){
SF("%d",&col[i]);
col1[i]=col[i];
}
dfs(1);
int b=3000,tag;
for(int i=1;i<=cnt;i++)
blo[i]=i/b+1;
for(int i=1;i<=m;i++){
SF("%d",&tag);
if(tag==1){
cntq++;
SF("%d%d",&q[cntq].l,&q[cntq].r);
q[cntq].cntc=cntc;
if(fir[q[cntq].l]>fir[q[cntq].r])
swap(q[cntq].l,q[cntq].r);
if(bac[q[cntq].l]>fir[q[cntq].r]){
x=bac[q[cntq].r];
y=bac[q[cntq].l];
q[cntq].p=0;
}
else{
x=bac[q[cntq].l];
y=fir[q[cntq].r];
q[cntq].p=1;
}
q[cntq].l=x;
q[cntq].r=y;
q[cntq].id=cntq;
}
else{
cntc++;
int pos,val;
SF("%d%d",&pos,&val);
p[cntc].pos=pos;
p[cntc].val=val;
p[cntc].rval=col1[pos];
col1[pos]=val;
}
}
sort(q+1,q+1+cntq,cmp);
Moqueue();
}