莫队算法小结

终于把糖果公园a掉了,写点小结冷静一下(由于博主现在思维混乱,所以请用混乱的思维来阅读本篇文章)

1、小z的袜子

这算是鼻祖了吧。

把序列分成sqrt(n)块,把询问先按左端点所在的块顺序,再按右端点升序排序,可以证名这样暴力移动左右端点最多达到O(n^1.5)的复杂度

简单吧

code是很就以前写的了,很丑勿喷

#include <cmath>
#include <cstdio>
#include <cstring>
#include <algorithm>
 
using namespace std;
typedef unsigned int UI;
const int Max=50005;
#define Sqrt(x) int (sqrt(x))
UI sum,ans1[Max],ans2[Max],cnt[Max];
int n,m,i,j,l,r,a[Max],pos[Max];
struct arr{
  int num,l,r;
  bool operator <(const arr &a)const
    {return (pos[l]<pos[a.l]) || (pos[l]==pos[a.l] && r<a.r);}
} q[Max];
 
UI gcd(UI x,UI y){
  while (y!=0)
   { int tmp=x; x=y; y=tmp%y; }
  return x;
}
 
int main(){
  scanf("%d%d",&n,&m);
  int d=Sqrt(m);
  for (i=1;i<=n;i++){
    scanf("%d",&a[i]);
    pos[i]=(i-1)/d+1;
  }
  for (i=1;i<=m;i++){
    scanf("%d%d",&q[i].l,&q[i].r);
    q[i].num=i;
  }
  sort(q+1,q+m+1);
   
  l=1, r=0;
  for (i=1;i<=m;i++){
    if (l<=q[i].l){
      for (;l<q[i].l;l++){
        cnt[a[l]]--;
        sum-=cnt[a[l]]*2+1;
      }
    } else
    {
      for (l--;l>=q[i].l;l--){
        sum+=cnt[a[l]]*2+1;
        cnt[a[l]]++;
      }
      l++;
    }
     
    if (r<=q[i].r){
      for (r++;r<=q[i].r;r++){
        sum+=cnt[a[r]]*2+1;
        cnt[a[r]]++;
      }
      r--;
    } else
    {
      for (;r>q[i].r;r--){
        cnt[a[r]]--;
        sum-=cnt[a[r]]*2+1;
      }
    }
     
    ans1[q[i].num]=sum-(q[i].r-q[i].l+1);
    ans2[q[i].num]=(q[i].r-q[i].l+1)*(q[i].r-q[i].l);
    if (ans1[q[i].num]==0) ans2[q[i].num]=1;
    UI tmp=gcd(ans1[q[i].num],ans2[q[i].num]);
    ans1[q[i].num]/=tmp; ans2[q[i].num]/=tmp;
  }
  for (i=1;i<=m;i++)
    printf("%u/%u\n",ans1[i],ans2[i]);
  return 0;
}

2、cf86D

基本和上一道一样,巩固以下代码

#include <cmath>
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;
const int Maxn = 200005;
typedef long long LL;
LL ans[Maxn], cnt;
int a[Maxn];
int sum[1000005];
int n,m,LEN;
struct QUERY
{
  int l,r,num;
  bool operator <(const QUERY &a)const
  {
    return (l-1)/LEN<(a.l-1)/LEN || ((l-1)/LEN==(a.l-1)/LEN && r<a.r);
  }
} q[Maxn];

int main(){
  freopen("86D.in","r",stdin);
  freopen("86D.out","w",stdout);
  scanf("%d%d",&n,&m);
  LEN = (int)sqrt(n);
  for (int i=1;i<=n;i++)
    scanf("%d",&a[i]);
  for (int i=1;i<=m;i++){
    scanf("%d%d",&q[i].l,&q[i].r);
    q[i].num=i;
  }
  sort(q+1,q+m+1);

  for (int i=q[1].l;i<=q[1].r;i++)
  {
    sum[a[i]]++;
    cnt += (LL)(sum[a[i]]*2-1)*a[i];
  }
  ans[q[1].num] = cnt;
  for (int i=2;i<=m;i++){
    if (q[i].l>q[i-1].l)
      for (int j=q[i-1].l;j<q[i].l;j++){
        cnt -= (LL)(sum[a[j]]*2-1)*a[j];
        sum[a[j]]--;
      }
    else
      for (int j=q[i].l;j<q[i-1].l;j++){
        sum[a[j]]++;
        cnt += (LL)(sum[a[j]]*2-1)*a[j];
      }

    if (q[i].r>q[i-1].r)
      for (int j=q[i-1].r+1;j<=q[i].r;j++){
        sum[a[j]]++;
        cnt += (LL)(sum[a[j]]*2-1)*a[j];
      }
    else
      for (int j=q[i].r+1;j<=q[i-1].r;j++){
        cnt -= (LL)(sum[a[j]]*2-1)*a[j];
        sum[a[j]]--;
      }

    ans[q[i].num] = cnt;
  }

  for (int i=1;i<=m;i++)
    printf("%lld\n",ans[i]);
  return 0;
}

3、bzoj3289

求区间逆序对个数

莫队+树状数组,O(n^1.5logn)水过,不解释。

#include <cmath>
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;
#define lowbit(x) ((x)&(-(x)))
const int Maxn = 50005;
typedef long long LL;
LL ans[Maxn], cnt;
int tr[Maxn], a[Maxn], b[Maxn];
int n,m,N,i,j,sum,LEN;
struct arr
{
  int l,r,num;
  bool operator <(const arr &a)const
  {
    return (l-1)/LEN>(a.l-1)/LEN || ((l-1)/LEN==(a.l-1)/LEN && r<a.r);
  }
} q[Maxn];

void add(int x,int t){
  for (int i=x;i<=n;i+=lowbit(i))
    tr[i] += t;
}

int query(int x){
  int ret = 0;
  for (int i=x;i>0;i-=lowbit(i))
    ret += tr[i];
  return ret;
}

void Mato_algorithm(){
  for (i=q[1].l;i<=q[1].r;i++){
    sum++;
    add(a[i],1);
    cnt += (LL)(sum-query(a[i]));
  }
  ans[q[1].num] = cnt;

  for (i=2;i<=m;i++){

    if (q[i].l<q[i-1].l)
      for (j=q[i-1].l-1;j>=q[i].l;j--){
        sum++;
        add(a[j],1);
        cnt += (LL)query(a[j]-1);
      }
    else
      for (j=q[i-1].l;j<q[i].l;j++){
        sum--;
        add(a[j],-1);
        cnt -= (LL)query(a[j]-1);
      }

    if (q[i].r<q[i-1].r)
      for (j=q[i-1].r;j>q[i].r;j--){
        sum--;
        add(a[j],-1);
        cnt -= (LL)(sum-query(a[j]));
      }
    else
      for (j=q[i-1].r+1;j<=q[i].r;j++){
        sum++;
        add(a[j],1);
        cnt += (LL)(sum-query(a[j]));
      }

    ans[q[i].num] = cnt;
  }
  for (i=1;i<=m;i++)
    printf("%lld\n",ans[i]);
}

int main(){
  freopen("3289.in","r",stdin);
  freopen("3289.out","w",stdout);
  scanf("%d",&n);
  LEN = (int)sqrt(n);
  for (i=1;i<=n;i++){
    scanf("%d",&a[i]);
    b[i] = a[i];
  }
  sort(b+1,b+n+1);
  N = unique(b+1,b+n+1)-b-1;
  for (i=1;i<=n;i++)
    a[i] = lower_bound(b+1,b+N+1,a[i])-b;

  scanf("%d",&m);
  for (i=1;i<=m;i++){
    scanf("%d%d",&q[i].l,&q[i].r);
    q[i].num=i;
  }
  sort(q+1,q+m+1);
  Mato_algorithm();
  return 0;
}

4、spoj cot2

莫队还能应用到树上?答案是肯定的!

只要能把树分块,莫队就能完成了!

树的分块参见vfk的blog,bzoj1096王室联邦

分完块之后可以像序列的方法搞起。

我们可以把前一个询问的左端点移动到当前的左端点,右端点类似。

把途径的所有点的状态取反并更新答案即可,举几个例子感受一下就行了。

需要注意的是我们在计算下一个询问的答案是需要把先把上一个询问左右端点的LCA去除,因为这个LCA会被很可能不会被删除。

细节见代码

#include <vector>
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;
const int Maxn = 100005;
int sum[Maxn], A[Maxn], B[Maxn];
int bc[Maxn], dep[Maxn], ans[Maxn];
int node[Maxn], next[Maxn], a[Maxn];
int fa[Maxn][17];
int n,m,SZ,tot,i,j,cnt,st,x,y;
bool vis[Maxn];
vector <int> e[Maxn];
struct QUERY
{
  int l,r,num;
  bool operator <(const QUERY &a)const
  {
    return bc[l]<bc[a.l] || (bc[l]==bc[a.l] && bc[r]<bc[a.r]);
  }
} q[Maxn];

void add(int x,int y){
  node[++tot]=y; next[tot]=a[x]; a[x]=tot;
  node[++tot]=x; next[tot]=a[y]; a[y]=tot;
}

void dfs(int x){
  //dfn[x] = ++tim;
  dep[x] = dep[fa[x][0]]+1;
  for (int i=a[x];i;i=next[i])
  if (fa[x][0]!=node[i]){
    int y = node[i];
    fa[y][0] = x;
    dfs(y);
    e[x].insert(e[x].begin(),e[y].begin(),e[y].end());
    e[y].clear();

    int len = e[x].size();
    if ( len>=SZ ){
      while (!e[x].empty()){
        bc[ e[x].back() ] = st;
        e[x].pop_back();
      }
      st++;
    }
  }
  e[x].push_back(x);
}

void init(){
  for (i=1;i<n;i++){
    scanf("%d%d",&x,&y);
    add(x,y);
  }
  dfs(1);
  while (!e[1].empty())
  {
    bc[ e[1].back() ] = st;
    e[1].pop_back();
  }
  for (j=1;j<=16;j++)
    for (i=1;i<=n;i++)
      fa[i][j] = fa[ fa[i][j-1] ][j-1];

  for (i=1;i<=m;i++){
    scanf("%d%d",&q[i].l,&q[i].r);
    q[i].num = i;
  }
  sort(q+1,q+m+1);
}

int LCA(int x,int y){
  if (dep[x]<dep[y]) swap(x,y);
  for (int i=16;i>=0;i--)
  if (dep[fa[x][i]]>=dep[y])
    x=fa[x][i];
  for (int i=16;i>=0;i--)
  if (fa[x][i]!=fa[y][i])
    x=fa[x][i], y=fa[y][i];
  if (x==y) return x;
  return fa[x][0];
}

void update(int x,int t){
  while (x!=t){
    if (vis[x]){
      if (--sum[A[x]] == 0) cnt--;
    } else
      if (++sum[A[x]] == 1) cnt++;
    vis[x] ^= 1;
    x = fa[x][0];
  }
}

void work(){
  init();

  int x1 = q[1].l, y1 = q[1].r, x2, y2;
  int t1 = LCA(x1, y1), t2;
  update(x1,t1); update(y1,t1);
  if (vis[t1]==0)
  {
    vis[t1] = 1;
    if (++sum[A[t1]] == 1) cnt++;
  }
  ans[ q[1].num ] = cnt;
  vis[t1] = 0;
  if (--sum[A[t1]] == 0) cnt--;
  for (int i=2;i<=m;i++){
    x1 = q[i].l, y1 = q[i].r;
    x2 = q[i-1].l, y2 = q[i-1].r;

    t1 = LCA(x1,x2);
    update(x1, t1);
    update(x2, t1);

    t2 = LCA(y1,y2);
    update(y1, t2);
    update(y2, t2);

    t1 = LCA(x1,y1);
    if (vis[t1]==0)
    {
      vis[t1] = 1;
      if (++sum[A[t1]] == 1) cnt++;
    }
    ans[ q[i].num ] = cnt;
    vis[t1] = 0;
    if (--sum[A[t1]] == 0) cnt--;
    
  }
}

int main(){
  freopen("cot2.in","r",stdin);
  freopen("cot2.out","w",stdout);

  scanf("%d%d",&n,&m);
  for (SZ=0;(SZ+1)*(SZ+1)<=n;SZ++);
  for (i=1;i<=n;i++)
    scanf("%d",&A[i]);
  memcpy(B,A,sizeof(A));
  sort(B+1,B+n+1);
  for (i=1;i<=n;i++)
    A[i] = lower_bound(B+1,B+n+1,A[i])-B;

  work();
  for (i=1;i<=m;i++)
    printf("%d\n",ans[i]);
  return 0;
}

3、wc2013糖果公园

这就是传说中八中上交一次卡一片的神题?

有修改还可以莫队?

强行莫队!

把修改操作提出来,询问依然排序。

在两个询问之间需要把这段内的修改操作暴力搞定,就是推进或者退回时间线

A:这不T得飞起?

Q:布吉岛。。。数据可过。。。不要在意这些细节。。。

另外需要把块的大小定为n^2/3级别的,因为时间会跑来跑去,所以这样总的复杂度为O(n^5/3)

吐槽一下:同样的code第一遍交ac,第二遍交就T了,看来ac也是要看评测机心情的。。。。

鬼畜的code

#include <vector>
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;
typedef long long LL;
const int Maxn = 200005;
LL ans[Maxn], cnt;
int node[Maxn], next[Maxn], a[Maxn];
int dep[Maxn], fa[Maxn][20], v[Maxn];
int w[Maxn], sum[Maxn], bc[Maxn];
int c[Maxn], cc[Maxn], stk[Maxn];
int n,m,Q,tot,SZ,st,x,y,i,j,type,top;
bool vis[Maxn];
struct QUERY
{
  int l,r,num,tim,go;
  bool operator <(const QUERY &a)const
  {
    if (bc[l]!=bc[a.l]) return (bc[l]<bc[a.l]);
    if (bc[r]!=bc[a.r]) return (bc[r]<bc[a.r]);
    return (tim<a.tim);
  }
} q[Maxn];
vector <int> e[Maxn];
struct arr{ int x,fr,to; };
vector <arr> chg;

int read(){
  char ch=getchar();
  while (ch<'0' || ch>'9') ch=getchar();
  int ret = 0;
  while (ch>='0'&&ch<='9')
    ret=ret*10+ch-'0', ch=getchar();
  return ret;
}

void add(int x,int y){
  node[++tot]=y; next[tot]=a[x]; a[x]=tot;
  node[++tot]=x; next[tot]=a[y]; a[y]=tot;
}

int splittree(int x){
  dep[x] = dep[fa[x][0]]+1;
  int size = 0;
  for (int i=a[x];i;i=next[i])
  if (fa[x][0]!=node[i]){
    int y = node[i];
    fa[y][0] = x;
    size += splittree(y);

    if (size>=SZ){
      while (size--){
        bc[ stk[top--] ] = st;
      }
      st++;
    }
  }
  stk[++top] = x;
  return size+1;
}

void init(){
  //scanf("%d%d%d",&n,&m,&Q);
  n = read(); m = read(); Q = read();
  //for (SZ=0;(SZ+1)*(SZ+1)<=n;SZ++);
  for (SZ=0;(SZ+1)*(SZ+1)*(SZ+1)<=n;SZ++);
  SZ *= SZ; SZ /= 3; SZ *= 2;
  for (i=1;i<=m;i++)
//    scanf("%d",&v[i]);
    v[i] = read();
  for (i=1;i<=n;i++)
//    scanf("%d",&w[i]);
    w[i] = read();
  for (i=1;i<n;i++){
    //scanf("%d%d",&x,&y);
    x = read();
    y = read();
    add(x,y);
  }

  splittree(1);
  while (top)
    bc[ stk[top--] ] = st;
  for (j=1;j<=17;j++)
    for (i=1;i<=n;i++)
      fa[i][j] = fa[ fa[i][j-1] ][j-1];
}

int LCA(int x,int y){
  if (dep[x]<dep[y])
    swap(x,y);
  for (int i=17;i>=0;i--)
  if (dep[fa[x][i]]>=dep[y])
    x=fa[x][i];
  if (x==y) return x;
  for (int i=17;i>=0;i--)
  if (fa[x][i]!=fa[y][i])
    x=fa[x][i], y=fa[y][i];
  return fa[x][0];
}

void modify(int x){
  if (vis[x]){
    cnt -= (LL)w[sum[c[x]]--] * v[c[x]];
  } else
  {
    cnt += (LL)w[++sum[c[x]]] * v[c[x]];
  }
  vis[x] ^= 1;
}

int update(int x,int y){
  if (dep[x]<dep[y]) swap(x,y);
  while (dep[x]>dep[y]){
    modify(x);
    x = fa[x][0];
  }
  while (x!=y){
    modify(x); x = fa[x][0];
    modify(y); y = fa[y][0];
  }
  if (x==y) return x;
  return fa[x][0];
}

void finish_change(int x,int col){
  if (vis[x]==0) c[x] = col;
  else
  {
    modify(x); 
    c[x] = col;
    modify(x);
  }
}

void change(int lasti,int curti){ // last time ---> current time
  if (lasti<=curti)
    for (int i=lasti;i<curti;i++)
      finish_change(chg[i].x,chg[i].to);
  else
    for (int i=lasti-1;i>=curti;i--)
      finish_change(chg[i].x,chg[i].fr);
}

void work(){
  for (i=1;i<=n;i++){
    //scanf("%d",&c[i]);
    cc[i] = c[i] = read();
  }
  for (i=1,tot=0;i<=Q;i++){
    //scanf("%d",&type);
    type = read();
    if (type==0){
      //scanf("%d%d",&x,&y);
      x = read(); y = read();
      chg.push_back((arr){x,cc[x],y});
      cc[x] = y;
    } else
    {
      tot++;
      //scanf("%d%d",&q[tot].l,&q[tot].r);
      q[tot].l = read(); q[tot].r = read();
      q[tot].num = tot; q[tot].tim=i;
      q[tot].go = chg.size();
    }
  }
  sort(q+1,q+tot+1);

  change(0,q[1].go);
  int t = update(q[1].l,q[1].r);
  modify(t); //add
  ans[q[1].num] = cnt;
  modify(t); //del
  for (i=2;i<=tot;i++){
    change(q[i-1].go,q[i].go);

    update(q[i].l,q[i-1].l);
    update(q[i].r,q[i-1].r);
    t = LCA(q[i].l,q[i].r);
    if (vis[t]==0)
      modify(t);
    ans[q[i].num] = cnt;
    modify(t);
  }
  for (i=1;i<=tot;i++)
    printf("%lld\n",ans[i]);
}

int main(){
  freopen("park.in","r",stdin);
  freopen("park.out","w",stdout);
  init();
  work();
  return 0;
}

最后在膜拜一下莫队算法~~~~

你可能感兴趣的:(莫队算法)