莫队

一、LuoGu P3709 大爷的字符串

描述:

给你一个字符串a,每次询问一段区间的贡献

贡献定义:每次从这个区间中随机拿出一个字符x,然后把x从这个区间中删除,你要维护一个集合S

如果S为空,你rp减1

如果S中有一个元素不小于x,则你rp减1,清空S

之后将x插入S

由于你是大爷,平时做过的题考试都会考到,所以每次询问你搞完这段区间的字符之后最多还有多少rp?rp初始为0

询问之间不互相影响。

 

吐槽:这个题题面十分恶心,看了半天没搞清要干什么,瞟了一下题解发现就是求区间众数,真看语文水平。

 

思路:

既然是区间众数,那么不就是直接按蒲公英的算法来?

的确可以,只是既然这个题都没有强制在线,我们是不是可以考虑发挥一下?

区间的话考虑莫队。比较套路的,我们可以记录下当前区间内数的出现次数,很容易维护,主要考虑如何统计答案。

这里要用到一个莫队中可能常需要用到的统计答案的方法:对答案分块。可以发现,分块统计答案仍然保证了复杂度的正确性。

在此题中,我们对次数分块,并对于每一个次数 都开一个数组记录 有多少个数出现了这么多次,然后对于每一次移动,相对应的修改次数的数组值就好了。

ps:事实证明我写莫队还是作死了一点,卡了好一会常才过,最慢的一个点999ms。

 

#include<bits/stdc++.h>
#define RG register
#define IL inline 
using namespace std;

IL int gi () {
    RG int x=0,w=0; char ch=0;
    while (ch<'0'||ch>'9') {if (ch=='-') w=1;ch=getchar();}
    while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
    return w?-x:x;
}

const int N=2e5+10;

int n,m,len,num,tot,a[N],b[N],c[N],blo[N],cnt[N],ans[N];

struct query {int id,l,r;}qur[N];
IL bool cmp (query A,query B) {return blo[A.l]==blo[B.l]?A.r<B.r:A.l<B.l;}

struct BLOCKS {int l,r,cnt,s[510];}B[510];

IL void modify (int pos,int v) {
    RG int cnum=cnt[a[pos]],bel=blo[cnum];
    if (cnum&&(--B[bel].s[cnum-B[bel].l+1]==0)) --B[bel].cnt;
    cnum=(cnt[a[pos]]+=v),bel=blo[cnum];
    if (cnum&&(++B[bel].s[cnum-B[bel].l+1]==1)) ++B[bel].cnt;
}

IL int Query () {
    RG int i,dir;
    for (i=num;i>=1;--i)
        if (B[i].cnt) {dir=i;break;};
    for (i=B[i].r;i;--i)
        if (B[dir].s[i]) return B[dir].l+i-1;
}

int main ()
{
    RG int i,x,y,Ldir=1,Rdir=0;
    n=gi(),m=gi(),len=sqrt(n),num=n/len+(n%len!=0);
    for (i=1;i<=n;++i) b[i]=a[i]=gi(),blo[i]=(i-1)/len+1;
    sort (b+1,b+n+1);
    for (i=1;i<=n;++i) if (b[i]!=b[i-1]) c[++tot]=b[i];
    for (i=1;i<=n;++i) a[i]=lower_bound(c+1,c+tot+1,a[i])-c;
    for (i=1;i<num;++i) B[i].l=(i-1)*len+1,B[i].r=len;
    B[num].l=(num-1)*len+1,B[num].r=n-B[num].l+1;
    for (i=1;i<=m;++i) qur[i].id=i,qur[i].l=gi(),qur[i].r=gi();
    sort (qur+1,qur+m+1,cmp);
    for (i=1;i<=m;++i) {
        while (Ldir>qur[i].l) modify(--Ldir,1);
        while (Ldir<qur[i].l) modify(Ldir++,-1);
        while (Rdir>qur[i].r) modify(Rdir--,-1);
        while (Rdir<qur[i].r) modify(++Rdir,1);
        ans[qur[i].id]=Query();
    }
    for (i=1;i<=m;++i) printf ("%d\n",-ans[i]);
    return 0;
}
BY BHLLX

 二、LuoGu P3674 小清新人渣的本愿

描述:

给你一个序列a,长度为n,有m次操作,每次询问一个区间是否可以选出两个数它们的差为x,或者询问一个区间是否可以选出两个数它们的和为x,或者询问一个区间是否可以选出两个数它们的乘积为x ,这三个操作分别为操作1,2,3

选出的这两个数可以是同一个位置的数。

 

思路:

对于1操作:我们可以维护一个数组now,记录每个数是否在当前区间中出现,对于询问只要考虑把这个01数组整体左移x位,和原数组与(&)一下, 至少有一位上为1就说明满足。

可是这样的话复杂度是线性的,所以我们需要用bitset优化。

对于2操作:考虑和上面的一样,但比较灵活一点。我们考虑如何构造:假设存在a+b=x,那么存在(N-a)-(N-x)=b。所以我们考虑另外维护一个bitset记录(N-这个数)是否出现,记为fnow。

那么对于询问,只需把fnow右移(N-x)位,再和now与(&)一下就好了。

对于3操作:乍一看不能和上面一样维护,但是考虑是乘积的形式,所以O(√n)的枚举一下约数就好了。

所以这个复杂度有点玄学,感觉有点不对,但是跑的挺快的。

 

#include<bits/stdc++.h>
#define RG register
#define IL inline 
using namespace std;

IL int gi () {
    RG int x=0,w=0; char ch=0;
    while (ch<'0'||ch>'9') {if (ch=='-') w=1;ch=getchar();}
    while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
    return w?-x:x;
}

const int N=1e5;

bitset<N+10> now,fnow;
int n,m,len,a[N+10],cnt[N+10],ans[N+10];

struct query {int id,typ,l,r,v,bel;}qur[N+10];
IL bool cmp (query A,query B) {return A.bel==B.bel?A.r<B.r:A.l<B.l;}

IL void add (int col) {if (++cnt[col]==1) now[col]=fnow[N-col]=1;}
IL void del (int col) {if (--cnt[col]==0) now[col]=fnow[N-col]=0;}

int main ()
{
    RG int i,j,ty,x,y,v,Ldir=1,Rdir=0;
    n=gi(),m=gi(),len=sqrt(n);
    for (i=1;i<=n;++i) a[i]=gi();
    for (i=1;i<=m;++i)
        ty=gi(),x=gi(),y=gi(),v=gi(),qur[i]=(query){i,ty,x,y,v,(x-1)/len+1};
    sort (qur+1,qur+m+1,cmp);
    for (i=1;i<=m;++i) {
        while (Ldir>qur[i].l) add(a[--Ldir]);
        while (Ldir<qur[i].l) del(a[Ldir++]);
        while (Rdir>qur[i].r) del(a[Rdir--]);
        while (Rdir<qur[i].r) add(a[++Rdir]);
        if (qur[i].typ==1) if ((now&(now<<qur[i].v)).any()) ans[qur[i].id]=1;     
        if (qur[i].typ==2) if ((now&(fnow>>(N-qur[i].v))).any()) ans[qur[i].id]=1;
        if (qur[i].typ==3) {
            for (j=1;j*j<=qur[i].v;++j)
                if (qur[i].v%j==0&&now[j]&&now[qur[i].v/j]) {ans[qur[i].id]=1;break;} 
        }
    }
    for (i=1;i<=m;++i) puts(ans[i]?"hana":"bi");
    return 0;
}
BY BHLLX

 

 WC2013 糖果公园 

描述:懒得蒯了   LuoGuP4074

思路:

树上待修莫队板子题。

和普通待修莫队不一样的地方:

1、询问分块方式。一般有两种分块方式:一种是dfs一遍,用栈记录,辅助分块。另一种是利用dfs序来分块。我比较喜欢前者。

2、统计答案时需要注意一些东西。假设:原本是a,b两点,且对于原来的有a,b间路径除LCA(a,b)外其他点都被标记为经过,现在是c,d两点。

主要讨论一下LCA的问题:

1°、如果LCA(a,b)还在c,d的路径上,那么必然至少一个点还在以LCA(a,b)为根的子树中,所以只会有至多一个点经过LCA(a,b),标记为经过。

  而LCA(c,d)不会有点经过,此时间c,d路径除LCA(c,d)外其他点都被标记为经过,符合假设。

2°、如果LCA(a,b)不在c,d的路径上,那么两个点都会经过LCA(a,b),相当于没操作依然没被标记,不影响,符合假设。

综上可知,我们每次移动后需对LCA处单独处理一下,统计完答案后还需还原。

#include<bits/stdc++.h>
#define RG register
#define IL inline
#define LL long long 
using namespace std;

IL int gi () {
    RG int x=0,w=0; char ch=0;
    while (ch<'0'||ch>'9') {if (ch=='-') w=1;ch=getchar();}
    while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
    return w?-x:x;
}

const int N=1e5+10;

LL Ans,ans[N];
int top,sta[N];
int tot,head[N];
int Tim,dfn[N],dep[N],f[N][21];
int n,m,q,qnum,a[N],V[N],W[N],C[N];
int len,bnum,md[N][2],blo[N],vis[N],cnt[N];

struct Edge {int next,to;}e[N<<1];
IL void make (int from,int to) {
    e[++tot]=(Edge){head[from],to};
    head[from]=tot;
}

struct Query {int id,u,v,T;}qur[N];
IL bool cmp (Query A,Query B) {
    if (blo[A.u]==blo[B.u])
        return (blo[A.v]==blo[B.v])?A.T<B.T:dfn[A.v]<dfn[B.v];
    return dfn[A.u]<dfn[B.u];
}

void dfs (int x,int fx) {
    RG int i,y,tp=top;
    f[x][0]=fx,dep[x]=dep[fx]+1,dfn[x]=++Tim;
    for (i=head[x];i;i=e[i].next) {
        if ((y=e[i].to)==fx) continue;
        dfs (y,x);
        if (top-tp>len) {
            ++bnum;
            while (top>tp) blo[sta[top--]]=bnum;
        }
    }
    sta[++top]=x;
}

IL void get_f () {
    for (RG int i=1;i<=20;++i)
        for (RG int j=1;j<=n;++j)
            f[j][i]=f[f[j][i-1]][i-1];
}

IL int get_lca (int x,int y) {
    RG int i;
    if (dep[x]<dep[y]) swap(x,y);
    for (i=20;i>=0;--i)
        if (dep[f[x][i]]>=dep[y]) x=f[x][i];
    if (x==y) return x;
    for (i=20;i>=0;--i)
        if (f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i];
    return f[x][0];
}

IL void add (int col) {Ans+=(LL)V[col]*W[++cnt[col]];}
IL void del (int col) {Ans-=(LL)V[col]*W[cnt[col]--];}
IL void remove (int pos) {(vis[pos]^=1)?add(C[pos]):del(C[pos]);}

IL void modify (int T) {
    if (vis[md[T][0]]) add(md[T][1]),del(C[md[T][0]]);
    swap (md[T][1],C[md[T][0]]);
}

IL void GO (int x,int y) {
    if (dep[x]<dep[y]) swap (x,y);
    while (dep[x]>dep[y]) remove(x),x=f[x][0];
    while (x!=y) remove(x),remove(y),x=f[x][0],y=f[y][0];
}

int main ()
{
    RG int i,op,x,y,T=0,Tdir=0,u=1,v=1,LCA;
    n=gi(),m=gi(),q=gi(),len=pow(n,2.0/3.0);
    for (i=1;i<=m;++i) V[i]=gi();
    for (i=1;i<=n;++i) W[i]=gi();
    for (i=1;i<n;++i) {
        x=gi(),y=gi();
        make (x,y),make (y,x);
    }
    for (i=1;i<=n;++i) C[i]=gi();
    for (i=1;i<=q;++i) {
        op=gi(),x=gi(),y=gi();
        if (op==1) qur[++qnum]=(Query){qnum,x,y,T};
        else md[++T][0]=x,md[T][1]=y;
    }
    dfs (1,1),get_f ();
    while (top) blo[sta[top--]]=bnum+1;++bnum;
    sort (qur+1,qur+qnum+1,cmp);
    for (i=1;i<=qnum;++i) {
        while (Tdir>qur[i].T) modify(Tdir--);
        while (Tdir<qur[i].T) modify(++Tdir);
        while (u!=qur[i].u) GO(u,qur[i].u),u=qur[i].u;
        while (v!=qur[i].v) GO(v,qur[i].v),v=qur[i].v;
        remove(LCA=get_lca(u,v)),ans[qur[i].id]=Ans,remove(LCA);
    }
    for (i=1;i<=qnum;++i) printf ("%lld\n",ans[i]);
    return 0;
}
BY BHLLX

 

posted @ 2018-12-22 17:26  薄荷凉了夏  阅读(202)  评论(0编辑  收藏  举报