【洛谷 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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值