洛谷P3245 [HNOI2016]大数

题目描述

小 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的倍数。

输入输出格式

输入格式:

第一行一个整数: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个询问的答案。

输入输出样例

输入样例#1: 
11
121121
3
1 6
1 5
1 4
输出样例#1: 
5
3
2
//第一个询问问的是整个串,满足条件的子串分别有:121121,2112,11,121,121。

说明

2016.4.19新加数据一组

原题30%数据直接暴力高精度模+二维前缀和,这里说100%数据:

莫队+后缀和

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]。

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

附代码:

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<map>
#include<cmath>
#include<cstring>
#define MAXN 100010
using namespace std;
map<long long,long long> ranks;
int n,m,p,val[MAXN];
long long front[MAXN],ans[MAXN],sum[MAXN],b[MAXN],num[MAXN];
struct node{
	int l,r,id;
}que[MAXN];
inline int read(){
	int date=0,w=1;char c=0;
	while(c<'0'||c>'9'){if(c=='-')w=-1;c=getchar();}
	while(c>='0'&&c<='9'){date=date*10+c-'0';c=getchar();}
	return date*w;
}
bool cmp1(const node &x,const node &y){
	return x.l<y.l;
}
bool cmp2(const node &x,const node &y){
	if(x.r==y.r)return x.l<y.l;
	return x.r<y.r;
}
void work1(){
    int l,r;
    for(int i=1;i<=n;i++){
        front[i]=front[i-1];sum[i]=sum[i-1];
        if(val[i]%p==0){
            front[i]+=i;
            sum[i]++;
        }
    }
    while(m--){
        l=read();r=read();
        printf("%lld\n",front[r]-front[l-1]-(sum[r]-sum[l-1])*(l-1));
    }
}
void work2(){
	int nowi=1,nowj=0,x,d,left=1,right=0,s=0;
	for(int i=1;i<=m;i++){
		que[i].l=read();que[i].r=read();
		que[i].id=i;
	}
	x=sqrt(n);
	sort(que+1,que+m+1,cmp1);
	while(nowi<=m){
		nowj++;
		d=nowi;
		while(que[nowi].l<nowj*x&&nowi<=m)nowi++;
		sort(que+d,que+nowi,cmp2);
		if(nowj==x){
			sort(que+d,que+m+1,cmp2);
			break;
		}
	}
	for(int i=n,j=1;i>=1;i--){
	    b[i]=sum[i]=(val[i]*j%p+sum[i+1])%p;
	    j=j*10%p;
	}
	sort(b+1,b+n+2);
	for(int i=1;i<=n+1;i++)ranks[b[i]]=i;
	for(int i=1;i<=n+1;i++)sum[i]=ranks[sum[i]];
	for(int i=1;i<=m;i++){
		while(que[i].l<left)
		{
			left--;
			s+=num[sum[left]];
			num[sum[left]]++;
		}
		while(que[i].l>left)
		{
		    s-=num[sum[left]]-1;
		    num[sum[left]]--;
			left++;
		}
		while(right<que[i].r+1)
		{
			right++;
			s+=num[sum[right]];
			num[sum[right]]++;
		}
		while(right>que[i].r+1)
		{
		    s-=num[sum[right]]-1;
		    num[sum[right]]--;
			right--;
		}
		ans[que[i].id]=s;
	}
	for(int i=1;i<=m;i++)printf("%lld\n",ans[i]);
}
void init(){
	char ch[MAXN];
	p=read();scanf("%s",ch);m=read();
	n=strlen(ch);
	for(int i=0;i<n;i++)val[i+1]=ch[i]-'0';
}
int main(){
	init();
	if(p==2||p==5)work1();
	else work2();
	return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值