【莫队】bzoj4542: [Hnoi2016]大数
挺有意思的,可以仔细体味一下的题;看白了就是莫队板子。
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个询问的答案。
题目分析
一个区间[l,r]产生贡献即$number_{i,j}\equiv 0\ ({\rm mod}\ p)$.
按照常见套路来说,应该把区间拆成关于端点的式子。用$pre[i]$表示前$i$位在十进制下的数值,那么即$number_{i,j}=pre[j]-10^{j-i+1}*pre[i-1]$。将式子移项发现左右两边形如$f(r)=f(l-1)$(据说这是一种经典的莫队套路)那么于此已经将区间问题转化为关于单点的可$O(1)$修改的子问题了。
之后的操作就相对套路:将$n+1$个$f(i)$(不要忘记统计$f(0)$)预先离散化处理;然后常规莫队操作。
注意莫队要先修改再更新!
1 #include<cmath> 2 #include<cstdio> 3 #include<cctype> 4 #include<cstring> 5 #include<algorithm> 6 const int maxn = 100035; 7 8 int blk[maxn]; 9 struct QRs 10 { 11 int l,r,id; 12 bool operator < (QRs a) const 13 { 14 if (blk[l]==blk[a.l]) return r < a.r; 15 return blk[l] < blk[a.l]; 16 } 17 }q[maxn]; 18 char s[maxn]; 19 int p,n,m,cnt,tot,size,inv10; 20 int lbd,rbd,lSd[maxn],rSd[maxn]; 21 int ans[maxn],sum[maxn],pre[maxn],t[maxn]; 22 23 int read() 24 { 25 char ch = getchar(); 26 int num = 0; 27 bool fl = 0; 28 for (; !isdigit(ch); ch=getchar()) 29 if (ch=='-') fl = 1; 30 for (; isdigit(ch); ch=getchar()) 31 num = (num<<1)+(num<<3)+ch-48; 32 if (fl) num = -num; 33 return num; 34 } 35 int qmi(int a, int b) 36 { 37 int ret = 1; 38 while (b) 39 { 40 if (b&1) ret = 1ll*ret*a%p; 41 b >>= 1, a = 1ll*a*a%p; 42 } 43 return ret; 44 } 45 void lAdd(int lbd) 46 { 47 rSd[sum[lbd]]++, tot+=rSd[sum[lbd-1]], lSd[sum[lbd-1]]++; 48 } 49 void rAdd(int rbd) 50 { 51 lSd[sum[rbd-1]]++, tot+=lSd[sum[rbd]], rSd[sum[rbd]]++; 52 } 53 void lErase(int lbd) 54 { 55 lSd[sum[lbd-1]]--, tot-=rSd[sum[lbd-1]], rSd[sum[lbd]]--; 56 } 57 void rErase(int rbd) 58 { 59 rSd[sum[rbd]]--, tot-=lSd[sum[rbd]], lSd[sum[rbd-1]]--; 60 } 61 int main() 62 { 63 freopen("bignum.in","r",stdin); 64 freopen("bignum.out","w",stdout); 65 p = read(), scanf("%s",s+1); 66 n = strlen(s+1), size = sqrt(n+0.5)+5; 67 if (p==2||p==5){ 68 for (int i=1; i<=n; i++) 69 { 70 ans[i] = ans[i-1], sum[i] = sum[i-1]; 71 if ((s[i]-'0')%p==0) ans[i]++, sum[i] += i; 72 } 73 m = read(); 74 for (int i=1; i<=m; i++) 75 { 76 int l = read(), r = read(); 77 printf("%d\n",sum[r]-sum[l-1]-(ans[r]-ans[l-1])*(l-1)); 78 } 79 return 0; 80 } 81 for (int i=1; i<=n; i++) blk[i] = i/size, pre[i] = (pre[i-1]*10ll%p+s[i]-'0')%p; 82 inv10 = qmi(10, p-2); 83 for (int i=0, pr=1; i<=n; i++) 84 t[i] = sum[i] = 1ll*pre[i]*pr%p, pr = 1ll*pr*inv10%p; 85 std::sort(t, t+n+1), cnt = std::unique(t, t+n+1)-t-1; 86 for (int i=0; i<=n; i++) 87 sum[i] = std::lower_bound(t, t+cnt+1, sum[i])-t+1; 88 m = read(); 89 for (int i=1; i<=m; i++) q[i].l = read(), q[i].r = read(), q[i].id = i; 90 std::sort(q+1, q+m+1); 91 lbd = 1, rbd = 0; 92 for (int i=1; i<=m; i++) 93 { 94 while (lbd < q[i].l) lErase(lbd++); 95 while (lbd > q[i].l) lAdd(--lbd); 96 while (rbd > q[i].r) rErase(rbd--); 97 while (rbd < q[i].r) rAdd(++rbd); 98 ans[q[i].id] = tot; 99 } 100 for (int i=1; i<=m; i++) printf("%d\n",ans[i]); 101 return 0; 102 }
END