bzoj 4542 洛谷 3245 [HNOI]2016大数 题解

题意简述

给你一个数字字符串$S$,长度为$n<=1e5$。有一个$int$范围内的质数$P$和$Q<=1e5$次询问,每次询问一个区间$[l,r]$中,$S$的多少个子串(不一定不同)是$P$的倍数。

比如$S=”007”$,它有6个子串,分别是$\{0,0,00,07,007\}$,这$6$个都是$7$的倍数。

思路框架

处理后缀和,分类讨论$p$和$10$是否互质。一个用莫队维护,一个用树状数组维护。

具体思路

先分类讨论。

1. P和10互质

那么末尾多几个$0$就不会影响对$P$的整除性。

维护后缀和$suf$,它表示从$i$开始的后缀接起来膜$P$的值。特殊地,$suf[n+1]=0$。对于一个区间$[l,r]$,当$suf[l]$和$suf[r+1]$相等的时候,$[l,r]$这一段就是$P$的倍数了。相当于我们要在$suf$数组中维护相等的无序对数,莫队即珂。

2. P不和10互质

即:P=2或5

这个时候我们就只需要判断末尾就好了。对于$2$的情况,那么子串结尾就是$0,2,4,6,8$。对于$5$的情况,子串结尾就是$0,5$。

假设询问$[l,r]$。我们找到一个$k$,如果以$k$为结尾的子串一定满足条件,那么这个$k$对答案的贡献就是$k-(l-1)$。(因为开始位置珂以是$[l,k]$中的任意一个,一共有$k-(l-1)$种情况)

我们先对$2$和$5$的情况分开讨论,然后开$20$个树状数组,其中$10$个维护长度为0~9的下标的和,另外$10$个维护$0~9$中的数量和。

对于$[l,r]$的询问,相当于要求和所有$k-(l-1)$。令满足条件$k$的数量是$C$,和为$S$。那么这一段答案就是$S-(l-1)\times C$。对于每一种尾数累加答案即珂。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186

#include <bits/stdc++.h>using namespace std;
namespace Flandre_Scarlet
{
#define N 155555
#define int long long
#define F(i,l,r) for(int i=l;i<=r;++i)
#define D(i,r,l) for(int i=r;i>=l;--i)
#define Fs(i,l,r,c) for(int i=l;i<=r;c)
#define Ds(i,r,l,c) for(int i=r;i>=l;c)
#define Tra(i,u) for(int i=G.Start(u),__v=G.To(i);~i;i=G.Next(i),__v=G.To(i))
#define MEM(x,a) memset(x,a,sizeof(x))
#define FK(x) MEM(x,0)

int n,p;
char s[N];
void R1(int &x)
{
x=0;char c=getchar();int f=1;
while(c<'0' or c>'9') f=(c=='-')?-1:1,c=getchar();
while(c>='0' and c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar();
x=(f==1)?x:-x;
}
void Input()
{
R1(p);scanf("%s",s+1);
n=strlen(s+1);
}


int a[N];
namespace SpecialCxk //p=2或5的情况
{
class BIT
{
public:
int tree[N];
int len;
void BuildTree(int _len)
{
len=_len;
FK(tree);
}
void Add(int pos,int val=1)
{
for(int i=pos;i<=len;i+=(i&(-i)))
{
tree[i]+=val;
}
}
int Query(int pos)
{
int ans=0;
for(int i=pos;i>0;i-=(i&(-i)))
{
ans+=tree[i];
}
return ans;
}
int RQuery(int l,int r)
{
return Query(r)-Query(l-1);
}
}T[10][2]; //T[i][j]: 值为i的所有位置的j次方和
//换句话说,j=0时计算数量,j=1时计算下标的和

void Soviet()
{
F(i,0,9) F(j,0,1) T[i][j].BuildTree(n+5);

if (p==2)
{
F(i,1,n)
{
T[a[i]][0].Add(i,1);
T[a[i]][1].Add(i,i); //对于第i个位置,一个加上1,一个加上i
//所以一个是数量和,一个是下标和
}
int q;R1(q);
F(i,1,q)
{
int l,r;
R1(l),R1(r);
int cnt0=T[0][0].RQuery(l,r);
int sum0=T[0][1].RQuery(l,r)-(l-1)*cnt0;

int cnt2=T[2][0].RQuery(l,r);
int sum2=T[2][1].RQuery(l,r)-(l-1)*cnt2;

int cnt4=T[4][0].RQuery(l,r);
int sum4=T[4][1].RQuery(l,r)-(l-1)*cnt4;

int cnt8=T[8][0].RQuery(l,r);
int sum8=T[8][1].RQuery(l,r)-(l-1)*cnt8;
//0 2 4 8分类讨论一下
printf("%lld\n",sum0+sum2+sum4+sum8); //累加
}
}
else if (p==5) //和p==2的情况没什么本质差别,是同样的思路
{
F(i,1,n)
{
T[a[i]][0].Add(i,1);
T[a[i]][1].Add(i,i);
}
int q;R1(q);
F(i,1,q)
{
int l,r;
R1(l),R1(r);
int cnt0=T[0][0].RQuery(l,r);
int sum0=T[0][1].RQuery(l,r)-(l-1)*cnt0;

int cnt5=T[5][0].RQuery(l,r);
int sum5=T[5][1].RQuery(l,r)-(l-1)*cnt5;
printf("%lld\n",sum0+sum5);
}
}
}
}

namespace Normal //p不是2或5的情况
{
int p10[N],suf[N];
map<int,int> disc;int dcnt=0;

int q,sn,ans[N];
struct node{int l,r,id;}Q[N];
bool cmp(node a,node b){return a.l/sn<b.l/sn or (a.l/sn==b.l/sn and a.r<b.r);}

int cur;
int cnt[N];
void Add(int x){cur+=cnt[x];cnt[x]++;}
void Del(int x){cnt[x]--;cur-=cnt[x];}
void Soviet()
{
p10[0]=1;F(i,1,n) p10[i]=p10[i-1]*10ll%p; //处理10的幂,方便维护suf数组
suf[n+1]=0; D(i,n,1) suf[i]=(suf[i+1]+p10[n-i]*a[i])%p; //求出后缀和数组suf
F(i,1,n+1)
{
if (!disc[suf[i]]) disc[suf[i]]=++dcnt;
suf[i]=disc[suf[i]];
} //把suf离散化,我用的是map离散化,懒得写排序+lowerbound了
//反正map也能过

sn=sqrt(n+0.5);
R1(q);
F(i,1,q)
{
int l,r;R1(l),R1(r);
Q[i]=(node){l,r+1,i}; //注意这里改成l,r+1
}
sort(Q+1,Q+q+1,cmp);
int ll=1,rr=0;cur=0;
F(i,1,q)
{
while(ll<Q[i].l) Del(suf[ll]),ll++;
while(rr>Q[i].r) Del(suf[rr]),rr--;
while(ll>Q[i].l) ll--,Add(suf[ll]);
while(rr<Q[i].r) rr++,Add(suf[rr]);

ans[Q[i].id]=cur;
} //莫队
F(i,1,q) printf("%lld\n",ans[i]);
}
}
void Soviet()
{
F(i,1,n) a[i]=s[i]-'0';
if (p==2 or p==5) SpecialCxk::Soviet();
else Normal::Soviet();
}

#define Flan void
Flan IsMyWife()
{
Input();
Soviet();
}
#undef int //long long
}
int main(){
Flandre_Scarlet::IsMyWife();
getchar();getchar();
return 0;
}
w