zoukankan      html  css  js  c++  java
  • 【笔记篇】莫队算法(一)

    P.S.:这个星期写了一个星期的莫队,现在也差不多理解了,下周该学点别的了(其实是被long long卡得生活不能自理......快要写吐了).
    在本文开始之前,先orz莫涛......

    莫队算法(Mo's algorithm),是一种离线解决区间问题的算法.
    据说,只要不强制在线,莫队算法能解决所有区间查询问题......

    如何判断一个问题可以使用莫队?
    如果我们知道[L,R]的答案,便可以O(1)推出[L-1,R] [L,R-1] [L+1,R] [L,R+1]的答案,就可以用莫队做...

    莫队算法的基本思想就是,把所有询问离线下来,得到一堆[L,R],我们已经知道上面的东西都可以O(1)求出了,那么我们对于[(L_2,R_2)]的答案就可以通过[(L_1,R_1)] ,花费(|L_2-L_1|+|R_2-R_1|)的时间求解....

    哦 那么我们是不是就可以通过改变询问的顺序来少些重复的转移?
    ——嗯,没错,二维平面MST(曼哈顿距离最小生成树)!!!
    莫队告诉我们,只要按这颗树做,复杂度就不会太高......而我们能在(O(nlog_2n))时间内求出MST...

    但这样的编程复杂度岂不是太高了?(MST对我等蒟蒻来说实在是……)
    但是不要紧,我们有偷懒的办法——分块的(O(nsqrt n))还是没问题的嘛= =(当然我们假设n,q同级)
    (orz 其实用MST做到最后的复杂度也是(O(nsqrt n)),只是常数小了点(别问我为啥,我不会证!!))
    我们把左端点的块编号作为第一关键字,把右端点的编号作为第二关键字排序...
    然后按排序好的序列推过去就行了orz....

    Q:为什么不是按左端点序号为第一关键字,右端点序号为第二关键字排序呢?
    A:是为了避免(L)略小于(L_1)略小于(L_2),但(R_1)远小于R远小于(R_2)的情况啊......

    关于复杂度的证明:
    右端点:由于排过序,左端点跨块时变得多,最多变n,有(sqrt n)个块所以不超过(O(nsqrt n))
    左端点:由于按左端点排序,跨两块最多也不超过(2sqrt n),有q个询问,q,n同级所以不超过(O(nsqrt n))
    而实际上的复杂度应该不到这个最坏复杂度,大概就O(玄学)了...

    嗯 差不多就是这样,下面我们看代码......

    我们做莫队的时候就是要分析:
    我们当前的区间维护到了[L,R],现在遇到了x点...
    -如果x∈[L,R]中,肯定要删除(不然就不会用到x点了)
    -如果x∉[L,R],我们就要添加
    所以我们可以用一个bool数组来维护每个点是不是在区间中(当然这份代码没有这么写)

    然后我们就需要一个fix函数来维护……

    void fix(int p,int &res)	//res用于维护结果,p表示更新p点
    {
    	if(ex[p])
    	{
    		//TODO:删掉p点要维护什么信息
    	}
    	else
    	{
    		//TODO:添加p点要维护什么信息
    	}
    	ex[p]^=1; //处理完p点要将p点存在性取反...
    }
    

    处理询问是怎么推过去的呢?

    //我们需要定义一个询问的结构体
    struct query
    {
    	int l,r,id; //第id个问题询问[l,r]
    };
    //我们之前需要一个cmp函数(sort用)
    bool cmp(const query &a,const query &b)
    {
    	if(a.l/blk==b.l/blk) return a.r<b.r; //blk表示分块的大小
    	return a.l<b.l;
    }
    
    void solve()
    {
    	sort(q+1,q+m,cmp); //将询问排序
    	int l=q[1].l,r=q[1].r-1;int res=0; //[l,r]表示当前处理到的区间,res表示结果
    	//开始的时候肯定不会想更新一堆信息所以把区间设为空
    	for(int i=1;i<=m;i++)
    	{
    		int L=q[i].l,R=q[i].r;
    		while(l>L) fix(--l,res);
    		while(l<L) fix(l++,res);
    		while(r>R) fix(r--,res);
    		while(r<R) fix(++r,res);
    		//这一串记住--l,其他都能推出来,至于为什么,也是很好理解的~
    		ans[q[i].id]=res;
    	}
    }
    //基本就是这样咯~
    

    例题? 是莫队在集训队论文中提出的.. bzoj上莫涛版权所有的一道题...
    bzoj2038-小z的袜子

    化式子什么的我本不想说,但是鉴于实在不是很懂orz...
    所以还是要化一下的...

    $ans=frac{sum C(cur[color_i],2)}{C(r-l+1,2)} ( )= frac{sum frac{cur[color_i]!}{(cur-2)!2!}}{frac{(r-l+1)!}{(r-l-1)!2!}} ( )= frac{sum curcolor_i}{(r-l)(r-l+1)}( )= frac{sum cur[color_i]^2-sum color_i}{(r-l)(r-l+1)}( )= frac{sum cur[color_i]^2+(r-l+1)}{(r-l)*(r-l+1)}$

    应该能看懂吧= = (color_i)表示i的颜色,cur[i]表示当前颜色为i的节点有多少...

    然后根据上面,我们就能得出这样代码:

    //此题极限数据50000*50000 一定要开long long
    //开long long的时候每一处乘法都要记得强转long long(WA惨的教训)
    #include <cmath>
    #include <cstdio>
    #include <algorithm>
    using namespace std;
    const int N=50005;
    typedef long long int64;
     
    int c[N],ex[N];
    int64 cur[N];
    int n,m,blk;
     
    struct _ans{
        int64 a,b;
    }ans[N];	//这题答案是个分数...
    struct query{
        int l,r,id;
    }q[N]; int ttt;
    bool cmp(const query &a,const query &b){
        if(a.l/blk==b.l/blk) return a.r<b.r;
        return a.l<b.l;
    }
    inline void buildquery(int l,int r,int id){
        q[id].l=l; q[id].r=r; q[id].id=id;
    }
     
     
    inline int getnum(){
        int a=0;char c=getchar();bool f=0;
        for(;(c<'0'||c>'9')&&c!='-';c=getchar());
        if(c=='-') c=getchar(),f=1;
        for(;c>='0'&&c<='9';c=getchar()) a=(a<<1)+(a<<3)+c-'0';
        if(f) return -a; return a;
    }
    int64 gcd(int64 a,int64 b){
        if(!b) return a;
        return gcd(b,a%b);
    }
     
    void fix(int p,int64 &res){
        if(ex[p]){
            res-=(cur[c[p]]<<1)-1;
            //这里是化了一下式子之后简便的位运算版(利用完全平方公式,可以自己推一下)
            cur[c[p]]--;
        }
        else{
            res+=(cur[c[p]]<<1|1); //同上
            cur[c[p]]++;
        }
        ex[p]^=1;
    }
    void solve(){
        sort(q+1,q+m+1,cmp);
        int l=q[1].l,r=q[1].l-1; int64 res=0;
        for(int i=1;i<=m;i++){
            int L=q[i].l,R=q[i].r,id=q[i].id;
            while(l>L) fix(--l,res);
            while(l<L) fix(l++,res);
            while(r<R) fix(++r,res);
            while(r>R) fix(r--,res);
            if(L==R) ans[id].a=0,ans[id].b=1; //特殊情况特殊处理
            else{
                int64 a=res-(r-l+1),b=(int64)(r-l+1)*(r-l),k=gcd(a,b);
                ans[id].a=a/k,ans[id].b=b/k;
            }
        }
    }
     
     
    int main(){
        n=getnum(),m=getnum(); blk=sqrt(n);
        for(int i=1;i<=n;i++) c[i]=getnum();
        for(int i=1;i<=m;i++){
            int x=getnum(),y=getnum();
            buildquery(x,y,i);
        } solve();
        for(int i=1;i<=m;i++) printf("%lld/%lld
    ",ans[i].a,ans[i].b);
    }
    

    就这样= = 完结撒花= =

  • 相关阅读:
    对MFC文档、视图、框架的理解
    MFC中快速将CVIew转换成CScrollView
    MFC中的一个错误
    单文档中视图与文档的相互
    python函数
    python模块介绍和引入
    python面向对象和面向过程
    python数据类型2
    python数据类型
    python无法使用input功能
  • 原文地址:https://www.cnblogs.com/enzymii/p/8412187.html
Copyright © 2011-2022 走看看