目录
[SCOI2013]数数
题目描述
输入描述:
输出描述:
输入
输出
说明
思路:
代码实现:
G-[SCOI2013]数数_牛客竞赛动态规划专题班数位dp练习 (nowcoder.com)
时间限制:C/C++ 1秒,其他语言2秒
空间限制:C/C++ 262144K,其他语言524288K
64bit IO Format: %lld
Fish 是一条生活在海里的鱼,有一天他很无聊,就开始数数玩。 他数数玩的具体规则是:
1. 确定数数的进制B
2. 确定一个数数的区间[L, R]
3. 对于[L, R] 间的每一个数,把该数视为一个字符串,列出该字符串的每一个(连续的)子串对应的B进制数的值。
4. 对所有列出的数求和。
现在Fish 数了一遍数,但是不确定自己的结果是否正确了。由于[L, R] 较大,他没有多余精力去验证是否正确,你能写一个程序来帮他验证吗?
输入包含三行。 第一行仅有一个数B,表示数数的进制。 第二行有N +1 个数,第一个数为N,表示数L在B 进制下的长度为N,接下里的N个数从高位到低位的表示数L 的具体每一位。 第三行有M+ 1 个数,第一个数为M,表示数R 在B 进制下的长度为M,接下里的M个数从高位到低位的表示数R 的具体每一位。 20% 数据,0 ≤ R ≤ L ≤ 10^5。 50% 数据,2 ≤ B ≤ 1000,1 ≤ N,M ≤ 1000。 100% 数据,2 ≤ B ≤ 10^5,1 ≤ N,M ≤ 10^5。
输出仅一行,即按照Fish 数数规则的结果,结果用10 进制表示,由于该数可能很大,输出该数模上20130427的模数。
示例1
复制10 3 1 0 3 3 1 0 3
10 3 1 0 3 3 1 0 3
复制120
120
Hint [103, 103] 之间仅有数103,该数的所有子串包括1, 10, 103, 0, 03, 3,其和为120。
还是用数位dp的思路,计算0-r的值v1和0-l的值v2,然后利用v1-v2,因为他是输入的字符串,你计算l-1还要考虑它的位之类的很烦,那我们直接单独计算l的贡献值v3,则答案就为v1-v2+v3。
现在给出计算思路:
public class Ex {
static int N = 100010;
static long[][][] dp = new long[N][2][2];
static int len;
static int B;
static int[] l= new int[N];
static int[] r = new int[N];
static int[] arr = new int[N];
static long[] pw = new long[N];
static long[] s = new long[N];
static long[] suf = new long[N];
static int mod = 20130427;
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
B = input.nextInt();
pw[1] = 1; s[1] = 1;
for(int i = 2; i < N; i ++) {
pw[i] = pw[i - 1] * B % mod;
s[i] = (s[i - 1] + pw[i] ) % mod;
}
int n = input.nextInt();
for(int i = 1; i <= n; i++) {
l[i] = input.nextInt();
}
int m = input.nextInt();
for(int i = 1; i <= m; i++) {
r[i] = input.nextInt();
}
// System.out.println(solve(r, m));
// System.out.println(solve(l, n));
// System.out.println(get(l, n));
System.out.println((solve(r, m) - solve(l, n) + mod+get(l,n)) % mod);
}
public static long get(int[] a, int n){
long res = 0;
for (int i = 1; i <= n; i++) {
res = (res + (long) i * a[i] % mod * s[n - i + 1] % mod) % mod;
}
return res;
}
public static long solve(int[] a, int n){
len = n;
arr = a;
for (int i = 0; i < N; i++) {
for (int j = 0; j < 2; j++) {
Arrays.fill(dp[i][j], -1);
}
}
suf[n + 1] = 0;
for (int i = n; i >= 1; i--) {
// 这里计算如果在有限制的情况下数据可能有多少种。即这样的贡献的贡献次数
suf[i] = (suf[i + 1] + pw[n - i + 1] * arr[i] % mod) % mod;
}
return dfs(1, 1, false)[0];
}
public static long[] dfs(int pos, int pre, boolean flag) {
if (pos > len) return new long[]{0,0};
int x = flag ? B-1:arr[pos];
if (flag && dp[pos][pre][0] != -1) return dp[pos][pre];
long[] ans = new long[2];
long[] tmp = dfs(pos + 1, pre, flag || x != 0); // 填0
// tmp[0] 下一位的贡献, pre == 1 ? 0 : 1) * tmp[1] 如果不是先导0,这里应该有额外贡献
ans[0] = (ans[0] + ((pre == 1 ? 0 : 1) * tmp[1]) + tmp[0]) % mod;
ans[1] = (ans[1] + tmp[1]) % mod;
if (x > 1) { //填 1 到 x - 1
long xx = x - 1;
long cnt = pw[len - pos + 1]; // 贡献次数
tmp = dfs(pos + 1, 0, true);
// s[len - pos + 1] * ( xx * x) / 2 % mod * cnt % mod 这一位提供的贡献
// tmp[0] * xx % mod 下一位提供的贡献
// tmp[1] * xx % mod 下一位提供的额外贡献
ans[0] = (ans[0] + s[len - pos + 1] * ( xx * x) / 2 % mod * cnt % mod + tmp[0] * xx % mod + tmp[1] * xx % mod) % mod;
// 计算这一位能提供的额外贡献
ans[1] = (ans[1] + s[len - pos + 1] * ( xx * x) / 2 % mod * cnt % mod + tmp[1] * xx % mod) % mod;
}
if (x > 0) { // 填x
long cnt = flag ? pw[len - pos + 1] : suf[pos + 1] + 1; // 贡献次数
tmp = dfs(pos + 1, 0, flag);
// 和上一个类似
ans[0] = ((long) s[len - pos + 1] * x % mod * cnt % mod + ans[0] + tmp[0] + tmp[1]) % mod;
ans[1] = ((long) s[len - pos + 1] * x % mod * cnt % mod + ans[1] + tmp[1]) % mod;
}
if (flag) dp[pos][pre] = ans;
return ans;
}
}