1. 程式人生 > >【bzoj4542】[Hnoi2016]大數 莫隊算法

【bzoj4542】[Hnoi2016]大數 莫隊算法

pri mes IT name 離散 莫隊 task 轉化 -s

題目描述

給出一個數字串,多次詢問一段區間有多少個子區間對應的數為P的倍數。其中P為質數。

輸入

第一行一個整數:P。第二行一個串:S。第三行一個整數:M。接下來M行,每行兩個整數 fr,to,表示對S 的子串S[fr…to]的一次詢問。註意:S的最左端的數字的位置序號為 1;例如S為213567,則S[1]為 2,S[1…3]為 213。

N,M<=100000,P為素數

輸出

輸出M行,每行一個整數,第 i行是第 i個詢問的答案。

樣例輸入

11
121121
3
1 6
1 5
1 4

樣例輸出

5
3
2


題解

莫隊算法

設 $b[i]=S[i...n]\ \text{mod}\ p$ ,那麽 $S[l,r]$ 為 $p$ 的倍數,當且僅當: $\frac{b[l]-b[r+1]}{10^{r-l}}\ \text{mod}\ p=0$ 。

當 $p=2$ 或 $p=5$ 時,直接通過數字串末位判斷是不是 $p$ 的倍數。設 $v[i]=[i\ \text{mod}\ p=0]$ ,維護 $v[i]$ 和 $v[i]·i$ 的前綴和即可快速得出答案。

當 $p\neq 2$ 且 $p\neq 5$ 時,$p$ 與 $10$ 互質。此時條件簡化為:$b[l]\equiv b[r+1]\ (\text{mod}\ p)$ 。

因此原問題轉化為:求 $[l,r+1]$ 內 $b$ 值相等的數對的數目。

將 $b$ 離散化,使用莫隊算法,用桶維護離散化後的某個 $b$ 的出現次數,指針移動時統計答案即可。

時間復雜度 $O(n\log n)$

#include <cmath>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 100010
using namespace std;
typedef long long ll;
ll p;
char str[N];
namespace task1
{
    ll s1[N] , s2[N];
    void solve()
    {
        int n , m , i , l , r;
        scanf("%s%d" , str + 1 , &m) , n = strlen(str + 1);
        for(i = 1 ; i <= n ; i ++ )
        {
            s1[i] = s1[i - 1] , s2[i] = s2[i - 1];
            if((str[i] - ‘0‘) % p == 0) s1[i] ++ , s2[i] += i;
        }
        while(m -- )
        {
            scanf("%d%d" , &l , &r);
            printf("%lld\n" , s2[r] - s2[l - 1] - (l - 1) * (s1[r] - s1[l - 1]));
        }
    }
}
namespace task2
{
    struct data
    {
        int l , r , bl , id;
        bool operator<(const data &a)const {return bl == a.bl ? r < a.r : bl < a.bl;}
    }q[N];
    int a[N] , mp[N];
    ll b[N] , v[N] , ans[N];
    void solve()
    {
        int n , m , i , si , lp = 1 , rp = 0;
        ll t = 1 , now = 0;
        scanf("%s%d" , str + 1 , &m) , n = strlen(str + 1) , si = (int)sqrt(n);
        for(i = n ; i ; i -- , t = t * 10 % p) v[i] = b[i] = (b[i + 1] + (str[i] - ‘0‘) * t) % p;
        v[n + 1] = 0 , sort(v + 1 , v + n + 2);
        for(i = 1 ; i <= n + 1 ; i ++ ) a[i] = lower_bound(v + 1 , v + n + 2 , b[i]) - v;
        for(i = 1 ; i <= m ; i ++ ) scanf("%d%d" , &q[i].l , &q[i].r) , q[i].r ++ , q[i].bl = (q[i].l - 1) / si , q[i].id = i;
        sort(q + 1 , q + m + 1);
        for(i = 1 ; i <= m ; i ++ )
        {
            while(lp > q[i].l) now += mp[a[--lp]] , mp[a[lp]] ++ ;
            while(rp < q[i].r) now += mp[a[++rp]] , mp[a[rp]] ++ ;
            while(lp < q[i].l) mp[a[lp]] -- , now -= mp[a[lp++]];
            while(rp > q[i].r) mp[a[rp]] -- , now -= mp[a[rp--]];
            ans[q[i].id] = now;
        }
        for(i = 1 ; i <= m ; i ++ ) printf("%lld\n" , ans[i]);
    }
}
int main()
{
    scanf("%lld" , &p);
    if(p == 2 || p == 5) task1::solve();
    else task2::solve();
    return 0;
}

【bzoj4542】[Hnoi2016]大數 莫隊算法