【bzoj4542&&luogu3245】[Hnoi2016] 大数 (莫队)

(http://www.elijahqi.win/2017/07/05/%E3%80%90bzoj4542luogu3245%E3%80%91hnoi2016-%E5%A4%A7%E6%95%B0-%E8%8E%AB%E9%98%9F/)
Description
小 B 有一个很大的数 S,长度达到了 N 位;这个数可以看成是一个串,它可能有前导 0,例如00009312345
。小B还有一个素数P。现在,小 B 提出了 M 个询问,每个询问求 S 的一个子串中有多少子串是 P 的倍数(0 也
是P 的倍数)。例如 S为0077时,其子串 007有6个子串:0,0,7,00,07,007;显然0077的子串007有6个子串都是素
数7的倍数。
Input
第一行一个整数:P。第二行一个串:S。第三行一个整数:M。接下来M行,每行两个整数 fr,to,表示对S 的
子串S[fr…to]的一次询问。注意:S的最左端的数字的位置序号为 1;例如S为213567,则S[1]为 2,S[1…3]为 2
13。N,M<=100000,P为素数
Output
输出M行,每行一个整数,第 i行是第 i个询问的答案。
Sample Input
11
121121
3
1 6
1 5
1 4
Sample Output
5
3
2
//第一个询问问的是整个串,满足条件的子串分别有:121121,2112,11,121,121。
HINT
Source
2016.4.19新加数据一组
Solution

据说新加的数据一组就是 p=2||p=5的数据

分类处理

1、p=2||p=5 用pre[]表示 前缀有多少个可以被p整除的子串 a[]表示有多少个可以被P整除的数 求区间多少个子串的时候用pre[r]-pre[l-1]-【l-1中整除p对区间l~r的贡献】
2、把n个后缀组成的数字全部对p取模。
若s[l] ~ s[n]的余数和s[r] ~ s[n]的余数相同,那么s[l] ~ s[r - 1]区间内的数字就是p的倍数(l < r)
然后这个题就变成经典莫队题了:给定一个序列,每次询问[l, r]内有多少对相同的数
每一个余数s[i]给一个计数器aa[i](需离散化),记录[l, r]中这个数出现了几次,区间长度±1时答案改变值为aa[i]。【推倒一下排列组合公式】同bzoj2038 jpy的方法

注意加和减aa[s[x]]-1 和aa[s[x]]

#include<cstdio>
#include<cmath>
#include<cstring>
#include<map>
#include<algorithm>
#define N 110000
using namespace std;
struct node{
    int l,r,id;
}data[N];
char str1[N];
map <long long,long long>mm;
int n,n1,m,a[N],aa[N];
long long pre[N],p,ans[N],map1[N],s[N],tmp;
inline int read(){
    int x=0;char ch=getchar();
    while (ch<'0'||ch>'9') ch=getchar();
    while (ch<='9'&&ch>='0'){x=x*10+ch-'0';ch=getchar();}
    return x;
}
bool cmp(node a,node b){
    if ((a.l-1)/n1==(b.l-1)/n1) return a.r<b.r;
    return (a.l-1)/n1<(b.l-1)/n1;
}
void work1(){
    for (int i=1;i<=n;++i){
        pre[i]=pre[i-1];a[i]=a[i-1];
        if ((str1[i]-'0')%p==0) pre[i]+=i,a[i]++;
    }
    for (int i=1;i<=m;++i){
        int l=read(),r=read();
        printf("%lld\n",pre[r]-pre[l-1]-(a[r]-a[l-1])*(l-1));
    }
}
inline void solve(int x){
    tmp+=aa[s[x]];aa[s[x]]+=1;
}
inline void solve1(int x){
    tmp-=aa[s[x]]-1;aa[s[x]]-=1;
}
int main(){
    freopen("3245.in","r",stdin);
    freopen("3245.out","w",stdout);
    scanf("%lld",&p);
    scanf("%s",str1);
    m=read();n=strlen(str1);
    for (int i=n;i>=1;--i) str1[i]=str1[i-1];
    if (p==5||p==2) {work1();return 0;}
    for (int i=1;i<=m;++i) data[i].l=read(),data[i].r=read(),data[i].id=i;
    n1=sqrt(n);
    sort(data+1,data+m+1,cmp);
    tmp=1;
    for (int i=n;i>=1;--i){
        s[i]=((str1[i]-'0')*tmp%p+s[i+1])%p;
        map1[i]=s[i];
        tmp=tmp*10%p;
    }
    //for (int i=1;i<=n+1;++i) printf("%d ",s[i]);printf("\n");
    sort(map1+1,map1+n+2);
    for (int i=1;i<=n+1;++i) mm[map1[i]]=i;
    for (int i=1;i<=n+1;++i) s[i]=mm[s[i]];
    //for (int i=1;i<=n+1;++i) printf("%d ",s[i]);printf("\n");
    int cl=1,cr=0;
    tmp=0;
    for (int i=1;i<=m;++i){
        int l=data[i].l,r=data[i].r+1,id=data[i].id;
        while (cl>l) solve(--cl);
        while (cl<l) solve1(cl++);
        while (cr<r) solve(++cr);
        while (cr>r) solve1(cr--);
        ans[id]=tmp;
    }
    for (int i=1;i<=m;++i) printf("%lld\n",ans[i]);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值