基础莫队算法

一,概述

  • 1,暴力的改进: 排序离线,query
  • 2,原理:右端点递增,左端点双指针一块移动,配图
  • 使用条件:
    1,只有询问没有修改(带修是…)
    2,一定能离线
    3,已知 [ l , r ] [l,r] [l,r]的答案,在 O ( 1 ) O(1) O(1) 的时间复杂度内就可以获取左右端点各延伸一个单位长度的区间的答案
  • 时间复杂度全貌: O ( n m + m l o g m ) O(n\sqrt{m}+mlogm) O(nm +mlogm) 维护加修改

二,莫队和整除分块

莫队的精髓就在于通过对询问进行排序,并把询问的结果作为下一个询问求解的基础,使得暴力求解的复杂度得到保证。

针对性优化

  • 1,我们的目的是使 l 和 r 两个指针走过的总距离尽量的小,这时候就要用到分块的思想了。
  • 2,把整个区间 [ 1 , n ] [1,n] [1,n] 分成若干块,以询问的左端点所在块为第一关键字,以询问的右端点大小为第二关键字,对询问进行排序

转化为了整除分块!

  • 对于同一块的询问,l 指针每次最多移动块的大小, r 指针的移动则是单调的,总共移动最多 n 。
  • 换块也是同理,不过是多遍历一个块的根号长度

实际上,“换块”这一事件发生的次数并不多,因此即便换块复杂度达到 常数 也是不会影响复杂度的。只不过一般情况下块是相邻的,换块复杂度就比较小。

三,习题

具体来说,小 Z 把这 N 只袜子从 1 到 N 编号,然后从编号 L 到 R (L 尽管小 Z 并不在意两只袜子是不是完整的一双,甚至不在意两只袜子是否一左一右,他却很在意袜子的颜色,毕竟穿两只不同色的袜子会很尴尬。

你的任务便是告诉小 Z,他有多大的概率抽到两只颜色相同的袜子。当然,小 Z 希望这个概率尽量高,所以他可能会询问多个 (L,R)以方便自己选择。

然而数据中有 L=R 情况,请特判这种情况,输出0/1。

  • 这是一道国集!
  • 袜子的匹配不是独立的,需要前期的袜子数辅助计算
  • 每次扩展一个袜子,满足区间局部可加性
  • 莫队!(bit也许也行)着色类型的基本都行
#include<algorithm>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<iostream>
const int N = 5e6 + 9;
typedef long long ll;
using namespace std;
typedef pair <ll, ll>pll;
int n, m, k;
ll cnt[N], w[N];
pll ans[N];
int len;

struct node
{
    int id, l, r;
} q[N];

ll gcd(ll a, ll b)
{
    if(b == 0)return a;
    else return gcd (b, a % b);
}

inline int g(int i)
{
    return i / len;
}

bool operator  < (const node &a, const node &b)
{
    if (g(a.l) == g(b.l))
    {
        if(g(a.l) % 2 == 1)return  a.r < b.r;
        else return a.r > b.r;
    }
    else return g(a.l) < g(b.l);
}

inline void del(int x, ll &res)
{
    res -= cnt[x] * (cnt[x] - 1);
    cnt[x]--;
    res += cnt[x] * (cnt[x] - 1);
}

inline void add(int x, ll &res)
{
    res -= cnt[x] * (cnt[x] - 1);
    cnt[x]++;
    res += cnt[x] * (cnt[x] - 1);
}

template<typename T> void read(T &x)
{
    x = 0;
    bool f = 1 ;
    char ch = getchar();
    while(ch > '9' || ch < '0'){if(ch == '-')f = 0; ch = getchar();}
    while (ch <= '9' && ch >= '0'){x = (x << 1) + (x << 3) + ch - '0'; ch = getchar();}
    if(!f)x *= -1;
}

inline void wri(ll x)
{
    if(x < 0)putchar('-'), x = -x;
    if(x > 9)wri(x / 10);
    putchar(x % 10 + '0');
    return ;
}

signed main(void)
{
    //freopen("P1494_1-1.in","r",stdin);
    //freopen("P1494_1-1.out","w",stdout);
    read(n);
    read(m);
    for(int i = 1 ; i <= n ; i++ )read(w[i]);
    len = sqrt(n);
    for (int i = 1 ; i <= m ; i++ )read(q[i].l), read(q[i].r), q[i].id = i;
    sort(q + 1, q + 1 + m);

    ll res = 0;

    for (int k = 1, i = 1, j = 0; k <= m ; k++ )
    {
        int l = q[k].l, r = q[k].r;
        if(l == r)
        {
            ans[q[k].id] = {0, 1};
            continue;
        }
        while (i < l)del(w[i++], res);
        while (i > l)add(w[--i], res);
        while (j > r)del(w[j--], res);
        while (j < r)add(w[++j], res);

        if(res == 0) ans[q[k].id] = {res, 1};

        else
        {
            ll gc = gcd(res, r-l);
            ans[q[k].id] = {res/gc, (r - l)/gc*(r - l + 1)};
        }
    }

    for(int i = 1; i <= m; i++)
    {
        ll gc = gcd(ans[i].first,ans[i].second);
        wri(ans[i].first / gc);
        putchar('/');
        wri(ans[i].second / gc);
        puts("");
    }
    return 0;
}

带修

  • 前面说过,普通的莫队只能解决没有修改的问题,那么带修改的问题怎么解决呢?带修莫队就是一种支持单点修改的莫队算法。

原理

  • 还是对询问进行排序,每个询问除了左端点和右端点还要记录这次询问是在第几次修改之后(时间),以左端点所在块为第一关键字,以右端点所在块为第二关键字,以时间为第三关键字进行排序。

  • 暴力查询时,如果当前修改数比询问的修改数少就把没修改的进行修改,反之回退。

  • 需要注意的是,修改分为两部分:

1,若修改的位置在当前区间内,需要更新答案(del原颜色,add修改后的颜色)。

2, 无论修改的位置是否在当前区间内,都要进行修改(以供add和del函数在以后更新答案)。

习题

墨墨购买了一套N支彩色画笔(其中有些颜色可能相同),摆成一排,你需要回答墨墨的提问。墨墨会向你发布如下指令:

1、 Q L R代表询问你从第L支画笔到第R支画笔中共有几种不同颜色的画笔。

2、 R P Col 把第P支画笔替换为颜色Col。

对于每一个Query的询问,你需要在对应的行中给出一个数字,代表第L支画笔到第R支画笔中共有几种不同颜色的画笔。

  • 着色类型:bit和莫队
  • 需要修改:带修莫队
  • 数颜色:满足区间极小可加性
  • 带修莫队!
pp
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
const int N = 133333+9;
int ans[N],w[N],n,m;
int len,cnt[1000009];
char ox[2];
int qx,tx;

struct query
{
    int id,t,l,r;
}q[N];

struct modify
{
    int op,d;
}c[N];

int g(int i){return (i-1)/len+1;}

bool operator < (query &x,query &y)
{
    if(g(x.l)!=g(y.l))return g(x.l)<g(y.l);
    else if(g(x.r)!=g(y.r))return g(x.r)<g(y.r);
    else return x.t< y.t;
}

template<typename T>inline  void read(T &x)
{
    x=0; int f=1; char ch = getchar();
    while (ch>'9'||ch<'0'){if(ch=='-')f=0; ch= getchar ();}
    while (ch>='0'&&ch <='9') {x=(x<<1)+(x<<3)+ch-'0'; ch = getchar();}
    if(!f)x*=-1;
}

inline void wri(int x)
{
    if(x>9)wri(x/10);
    putchar(x%10+'0');
    return ;
}

inline void add(int x,int &res)
{
    if(!cnt [x])res++;
    cnt[x]++;
}
inline void del (int x,int &res)
{
    cnt[x]--;
    if(!cnt[x])res--;
}

signed main(void)
{
    int a,b;

    read(n); read(m);

    for(int i=1;i<=n;i++)read(w[i]);
    for(int i=1;i<=m;i++)
    {
        scanf("%s",ox);
        read(a); read(b);
        if(*ox=='Q')q[++qx]=(query){qx,tx,a,b};
        else c[++tx]=(modify){a,b};
    }

    len=cbrt((double)n*n)+1;
    sort(q+1,q+1+qx);

    for(int k=1,i=1,j=0,res=0,ti=0; k<=qx;k++)
    {
        int l= q[k].l; int r= q[k].r; int t=q[k].t;
        while (i<l)del(w[i++],res);
        while (i>l)add(w[--i],res);
        while (j<r)add(w[++j],res);
        while (j>r)del(w[j--],res);

        while (ti>t)
        {
            if(c[ti].op<=r&&c[ti].op>=l)del(w[c[ti].op],res),add(c[ti].d,res);
            swap(w[c[ti].op],c[ti].d);
            ti--;
        }

        while (ti<t)
        {
            ti++;
            if(c[ti].op<=r && c[ti].op>=l)del(w[c[ti].op],res),add(c[ti].d,res);
            swap(w[c[ti].op],c[ti].d);
        }
        ans[q[k].id]=res;
    }

    for(int i=1;i<=qx;i++)wri(ans[i]),puts("");
    return 0;
}



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

流苏贺风

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值