莫队算法小结

27 篇文章 0 订阅
25 篇文章 1 订阅

终于把糖果公园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;
}

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值