【洛谷 P3281】 [SCOI2013]数数
作者:互联网
题目描述
Fish 是一条生活在海里的鱼,有一天他很无聊,就开始数数玩。他数数玩的具体规则是:
确定数数的进制B
确定一个数数的区间[L, R]
对于[L, R] 间的每一个数,把该数视为一个字符串,列出该字符串的每一个(连续的)子串对应的B进制数的值。
对所有列出的数求和。现在Fish 数了一遍数,但是不确定自己的结果是否正确了。由于[L, R] 较大,他没有多余精力去验证是否正确,你能写一个程序来帮他验证吗?
输入格式
输入包含三行。
第一行仅有一个数B,表示数数的进制。
第二行有N +1 个数,第一个数为N,表示数L 在B 进制下的长度为N,接下里的N个数从高位到低位的表示数L 的具体每一位。
第三行有M+ 1 个数,第一个数为M,表示数R 在B 进制下的长度为M,接下里的M个数从高位到低位的表示数R 的具体每一位。
输出格式
输出仅一行,即按照Fish 数数规则的结果,结果用10 进制表示,由于该数可能很大,输出该数模上20130427的模数。
输入输出样例
输入 #1 复制
10
3 1 0 3
3 1 0 3
输出 #1 复制
120
说明/提示
【样例解释】
[103, 103] 之间仅有数103,该数的所有子串包括1, 10, 103, 0, 03, 3,其和为120。
【数据范围与约定】
20% 数据,0 <= R <= L <= 10^5。
50% 数据,2 <= B <= 1000,1 <= N,M <= 1000。
100% 数据,2 <= B <= 10^5,1 <= N,M <= 10^5。
思路
首先我们考虑解决这样一个问题,求[1,x-1)内所有数的答案
把x写成B进制形式 x1, x2, x3, … , xn。
首先考虑不足n位的数
然后对于达到了n位的数,枚举第一个小于xi的数位。
由于是开区间,必然存在这样一个数位。
这种方法可以减少前导零的分类,还是比较方便的
下面讲一下这道题的DP
这道题是融合了很浓厚的数学思想的
下文设P[i] = 10^i,S[i]为P[i]前缀和
考虑给定一个数x1, x2, …, xn,求它的子串和。
考虑每个数位的贡献,第i个数位的贡献是
i * xi * S[n - i]
理解一下这个式子
定义d[i][0]表示还要填i位,所有前缀子串和,d[i][1]表示还要填i位,所有子串和。
这个转移还是比较显然的
对于不足n位的数,显然直接枚举最高位就行了
达到n位的数,我们需要考虑三段的贡献,即:第一个小于xi的数位,第一个小于xi的数位之前,第一个小于xi的数位之后
具体情况可以看一下下面的代码
然而我们还有一个问题没有解决
上面的做法是O(nB)的。
只要把枚举该位填了什么改成通过
1 + 2 + … + n = n * (n + 1) / 1
优化一下即可
代码的SV表示原数x的一段前缀在整个数上的贡献
千万注意每次DP前清零SV。
代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int mod = 20130427;
int cnt, a[100005];
int B, SV[100005], d[100005][2], P[100005], S[100005];
void read()
{
memset(SV, 0, sizeof(SV));
for(int i = cnt - 1; i >= 0; i--)
scanf("%lld", &a[i]), SV[i] = (SV[i + 1] + a[i] * (cnt - i) % mod * S[i] % mod) % mod;
}
int calc(int x, bool p)
{
if(x <= 0) return 0;
if(d[x][p] != -1) return d[x][p];
if(p)
return d[x][1] = (B * calc(x - 1, 1) % mod + (B * (B - 1) / 2) % mod * P[x - 1] % mod * S[x - 1] % mod + B * calc(x - 1, 0) % mod) % mod;
else
return d[x][0] = (B * calc(x - 1, 0) % mod + (B * (B - 1) / 2) % mod * P[x - 1] % mod * S[x - 1] % mod) % mod;
}
int solve()
{
int i, ans = 0;
for(i = 1; i < cnt; i++)
ans = (ans + (B - 1) * calc(i - 1, 1) % mod + (B * (B - 1) / 2) % mod * P[i - 1] % mod * S[i - 1] % mod + (B - 1) * calc(i - 1, 0) % mod) % mod;
for(i = cnt - 1; i >= 0; i--)
if(i == cnt - 1)
ans = (ans + (a[i] * (a[i] - 1) / 2) % mod * P[i] % mod * S[i] % mod + (a[i] - 1) * calc(i, 1) % mod + (a[i] - 1) * calc(i, 0) % mod) % mod;
else
ans = (ans + SV[i + 1] * a[i] % mod * P[i] % mod + (a[i] * (a[i] - 1) / 2) % mod * P[i] % mod * S[i] % mod * (cnt - i) % mod + a[i] * calc(i, 1) % mod + a[i] * calc(i, 0) % mod * (cnt - i) % mod) % mod;
return ans;
}
int count()
{
return SV[0];
}
signed main()
{
int a1, a2, c1, c2;
memset(d, -1, sizeof(d));
scanf("%lld%lld", &B, &cnt);
P[0] = S[0] = 1;
for(int i = 1; i <= 100003; i++)
P[i] = P[i - 1] * B % mod, S[i] = (S[i - 1] + P[i]) % mod;
read();
c1 = count();
a1 = solve();
scanf("%lld", &cnt);
read();
c2 = count();
a2 = solve();
printf("%lld\n", (a2 - a1 + c2 + mod * mod) % mod);
return 0;
}
标签:cnt,洛谷,int,数数,SV,P3281,数位,SCOI2013,mod 来源: https://blog.csdn.net/Eric1561759334/article/details/100576465