zoukankan      html  css  js  c++  java
  • [学习笔记]莫队算法

    莫队算法

    确实是看过的最良心的讲解: https://www.cnblogs.com/CsOH/p/5904430.html

    • 问题:有n个数组成一个序列,有m个形如询问L, R的询问,每次询问需要回答区间内至少出现2次的数有哪些。

    朴素的解法需要读取O(nm)次数。如果数据范围小,可以用数组,时间复杂度为O(nm)。如果使用STL的Map来保存出现的次数,则需要O(nmlogn)的复杂度。有没有更快的方法呢?

    注意到询问并没有强制在线,因此我们可以使用离线方法。注意到一点,如果我们有计算完[L, R]时的“中间变量”(在本题为每个数出现的次数),那么[L - 1, R]、[L + 1, R]、[L, R - 1]、[L, R + 1]都能够在“中间变量”的“基本操作时间复杂度”得出。如果能安排适当的询问顺序,使得每次询问都能用上上次运行产生的中间变量,那么我们将可以在更优的复杂度完成整个询问。

    如果数据较小,用数组,时间复杂度为O(1);如果数据较大,可以考虑用离散化或map,时间复杂度为O(logn)。

    那如何安排询问呢?这里有个时间复杂度非常优秀的方法:首先将每个询问视为一个“点”,两个点P1, P2之间的距离为abs(L1 - L2) + abs(R1 - R2),即曼哈顿距离,然后求这些点的最小生成树,然后沿着树边遍历一次。由于这里的距离是曼哈顿距离,所以这样的生成树被称为“曼哈顿最小生成树”。最小曼哈顿生成树有专用的算法,求生成树时间复杂度可以仅为O(mlogm)。

    其实这里是建边的算法,建边后依然使用传统的Prim或者Kruskal算法来求最小生成树。

    不幸的是,曼哈顿最小生成树的写法很复杂,考场上不建议这样做.  
    一种直观的办法是按照左端点排序,再按照右端点排序。但是这样的表现不好。特别是面对精心设计的数据,这样方法表现得很差。
      举个例子,有6个询问如下:(1, 100), (2, 2), (3, 99), (4, 4), (5, 102), (6, 7)。
      这个数据已经按照左端点排序了。用上述方法处理时,左端点会移动6次,右端点会移动移动98+97+95+98+95=483次。右端点大幅度地来回移动,严重影响了时间复杂度——排序的复杂度是O(mlogm),所有左端点移动次数仅为为O(n),但右端点每个询问移动O(n),共有m个询问,故总移动次数为O(nm),移动总数为O(mlogm + nm)。运行时间上界并没有减少。
      其实我们稍微改变一下询问处理的顺序就能做得更好:(2, 2), (4, 4), (6, 7), (5, 102), (3, 99), (1, 100)。
      左端点移动次数为2+2+1+2+2=9次,比原来稍多。右端点移动次数为2+3+95+3+1=104,右端点的移动次数大大降低了。
      上面的过程启发我们:①我们不应该严格按照升序排序,而是根据需要灵活一点的排序方法;②如果适当减少右端点移动次数,即使稍微增多一点左端点移动次数,在总的复杂度上看,也是划算的。
      在排序时,我们并不是按照左右端点严格升序排序询问,而只是令其左右端点处于“大概是升序”的状态。具体的方法是,把所有的区间划分为不同的块,将每个询问按照左端点的所在块序号排序,左端点块一样则按照右端点排序。注意这个与上一个版本的不同之处在于“第一关键字”是左端点所在块而非左端点。
      莫队算法首先将整个序列分成√n个块(同样,只是概念上分的块,实际上我们并不需要严格存储块),接着将每个询问按照块序号排序(一样则按照右端点排序)。之后,我们从排序后第一个询问开始,逐个计算答案。

    模板题:Harvest of Apples

    纪念再一次打铁和第一次倒数第一

    题目描述

    There are n apples on a tree, numbered from 1 to n.
    Count the number of ways to pick at most m apples.

    输入

    The first line of the input contains an integer T (1≤T≤105) denoting the number of test cases.
    Each test case consists of one line with two integers n,m (1≤m≤n≤105).

    输出

    For each test case, print an integer representing the number of ways modulo 109+7.

    样例输入

    2
    5 2
    1000 500

    样例输出

    16
    924129523

    组合数的前缀和
    将询问离线下来
    根据公式递推S(m,n)=2*S(m-1,n)-C(m-1,n-1)来add或erase

    #include <cstdio>
    #include <cstring>
    #include <queue>
    #include <cmath>
    #include <algorithm>
    #include <set>
    #include <iostream>
    #include <map>
    #include <stack>
    #include <string>
    #include <vector>
    #define ll long long
    #define ull unsigned long long
    #define inf 0x3f3f3f3f
    #define mp make_pair
    #define met(a,x) memset(a,x,sizeof(a))
    using namespace std;
    const int maxn=1e5+7;
    ll ans[maxn],two;
    int block;
    ll fac[maxn],inv[maxn];
    const int mod=1e9+7;
    struct node{
        int l,r,i;
        bool operator<(const node&c) const {
            if(l/block==c.l/block){
                return r/block<c.r/block;
            }
            else return l/block<c.l/block;
        }
    }s[maxn];
    ll qpow(ll a,ll n){
        ll res=1;
        while (n){
            if(n&1)res=res*a%mod;
            a=a*a%mod;
            n>>=1;
        }
        return res;
    }
    void init(){
        fac[0]=1;
        for (ll i = 1; i < maxn; ++i) {
            fac[i]=fac[i-1]*i%mod;
        }
        inv[maxn-1]=qpow(fac[maxn-1],mod-2);
        for (ll i = maxn-2; i>=0 ; --i) {
            inv[i]=inv[i+1]*(i+1)%mod;
        }
        two=qpow(2,mod-2);
    }
    ll C(int m, int n){
        if(!m||!n) return 1;
        if(m<n) return 0;
        return fac[m]*inv[n]%mod*inv[m-n]%mod;
    }
    int main(){
        init();
        int t;scanf("%d",&t);
        for (int i = 1; i <=t ; ++i) {
            scanf("%d%d",&s[i].l,&s[i].r);
            s[i].i=i;
        }
        block=sqrt(1e5);
        sort(s+1,s+1+t);ll sum=1;
        for (int i = 1,l=1,r=0; i <=t ; ++i) {
            while (l<s[i].l){sum=(2*sum%mod-C(l++,r)+mod)%mod;}
            while (l>s[i].l){sum=(sum+C(--l,r))%mod*two%mod;}
            while (r<s[i].r){sum=(sum+C(l,++r))%mod;}
            while (r>s[i].r){sum=(sum-C(l,r--)+mod)%mod;}
            ans[s[i].i]=sum;
        }
        for (int i = 1; i <=t; ++i) {
            printf("%lld
    ",ans[i]);
        }
        return 0;
    }
    
    
    不要忘记努力,不要辜负自己 欢迎指正 QQ:1468580561
  • 相关阅读:
    ES 设置管理
    ES 数据搜索(1)
    ES 集群管理及基本操作
    ES 安装
    ES 基本概念
    HBase 缓存
    【Linux】【8】切换JDK版本时报错,bash: ./java: cannot execute binary file
    【Linux】【7】常用命令-目录处理命令
    【Linux】【6】Java项目打成Jar包后部署至服务器上
    【Linux】【5】安装jdk1.8并配置环境变量,以及切换jdk
  • 原文地址:https://www.cnblogs.com/smallocean/p/9695234.html
Copyright © 2011-2022 走看看