[SCOI2013]数数(数位dp)

题意

在这里插入图片描述

在这里插入图片描述

分析

这道破题怎么写了我这么久啊啊!!菜死了TAT
很明显要数位 d p dp dp
n u m p o s , l i m i t , z e r o num_{pos,limit,zero} numpos,limit,zero 表示从高到低考虑到第 p o s pos pos 位,有没有顶着上界,是否有前导零的数的个数, s 1 p o s , l i m i t , z e r o s1_{pos,limit,zero} s1pos,limit,zero 表示从高到低考虑到第 p o s pos pos 位,有没有顶着上界,是否有前导零的前缀串之和,比如 12345 12345 12345 的前缀串是 1 , 12 , 123 , 1234 , 12345 1,12,123,1234,12345 1,12,123,1234,12345。那么转移的话枚举当前最高位的数 i i i,考虑 i i i 对单独一个 p o s pos pos 位数的贡献,令 T i = ∑ j = 0 i B j T_{i}=\sum\limits_{j=0}^{i}B^j Ti=j=0iBj,那贡献是 i ∗ T p o s i*T_{pos} iTpos。总共有 n u m num num 个数,因此 s 1 p o s , l i m i t , z e r o = ∑ i = 0 u p s 1 p o s − 1 , l i m i t & & i = = u p , z e r o & & i = = 0 + i ∗ T p o s ∗ n u m s1_{pos,limit,zero}=\sum\limits_{i=0}^{up}s1_{pos-1,limit\&\&i==up,zero\&\&i==0}+i*T_{pos}*num s1pos,limit,zero=i=0ups1pos1,limit&&i==up,zero&&i==0+iTposnum。设 s 2 p o s , l i m i t , z e r o s2_{pos,limit,zero} s2pos,limit,zero 表示从高到低考虑到第 p o s pos pos 位,有没有顶着上界,是否有前导零的所有子串之和。那么转移是类似的,就是 p o s − 1 pos-1 pos1 位的子串和加上最高位参与的前缀串之和即可。需要注意的是,如果有前导 0 0 0,那最高位选 0 0 0 前缀串之和是不能算进 s 2 s2 s2 的。
这样子的复杂度是 O ( N B ) O(NB) O(NB) 的。
然后发现转移的时候,只用分三种情况, i = 0 i=0 i=0 i = u p i=up i=up i ∈ [ 1 , u p − 1 ] i\in[1,up-1] i[1,up1]。第三种的每个 i i i 是等价的,可以通过等差数列求和来快速得到。转移复杂度这样子就降到了 O ( 1 ) O(1) O(1)
总之是一道不是很难的题(为什么我感觉这么难啊

代码如下(注释掉的部分是暴力转移的)

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;

void debug_out(){
    cerr << endl;
}
template<typename Head, typename... Tail>
void debug_out(Head H, Tail... T){
    cerr << " " << to_string(H);
    debug_out(T...);
}
#ifdef local
#define debug(...) cerr<<"["<<#__VA_ARGS__<<"]:",debug_out(__VA_ARGS__)
#else
#define debug(...) 55
#endif

const int N = 1e5 + 5, maxn = 1e5, mod = 20130427;
int T[N], a[N], B, ans;
struct DP{
	int n, s1, s2;
}dp[N][2][2];
int vis[N][2][2];
DP dfs(int pos, int f1, int f2){
	if(pos == -1) return {1, 0, 0};
	if(vis[pos][f1][f2]) return dp[pos][f1][f2];
	vis[pos][f1][f2] = 1;
	int up = f1? a[pos]: B - 1;
	DP res = {0, 0, 0};
	/*for(int i = 0; i <= up; i++){
		DP t = dfs(pos - 1, f1 && i == up, f2 && i == 0);
		res.n = (res.n + t.n) % mod;
		res.s1 = (res.s1 + (LL)t.n * i % mod * T[pos] % mod + t.s1) % mod;
		res.s2 = (res.s2 + t.s2 + (LL)t.n * i % mod * T[pos] % mod + t.s1 * (!(f2 && i == 0))) % mod;
	}*/
	int i = 0;
	DP t = dfs(pos - 1, f1 && i == up, f2 && i == 0);
	res.n = (res.n + t.n) % mod;
	res.s1 = (res.s1 + (LL)t.n * i % mod * T[pos] % mod + t.s1) % mod;
	res.s2 = (res.s2 + t.s2 + (LL)t.n * i % mod * T[pos] % mod + t.s1 * (!(f2 && i == 0))) % mod;
	if(up > 0){
		i = up;
		t = dfs(pos - 1, f1 && i == up, f2 && i == 0);
		res.n = (res.n + t.n) % mod;
		res.s1 = (res.s1 + (LL)t.n * i % mod * T[pos] % mod + t.s1) % mod;
		res.s2 = (res.s2 + t.s2 + (LL)t.n * i % mod * T[pos] % mod + t.s1 * (!(f2 && i == 0))) % mod;
		if(up > 1){
			int s = ((LL)up * (up - 1) / 2) % mod;
			t = dfs(pos - 1, 0, 0);
			res.n = (res.n + (LL)t.n * (up - 1) % mod) % mod;
			res.s1 = (res.s1 + (LL)t.n * s % mod * T[pos] % mod + (LL)t.s1 * (up - 1) % mod) % mod;
			res.s2 = (res.s2 + (LL)t.s2 * (up - 1) + (LL)t.n * s % mod * T[pos] % mod + (LL)t.s1 * (up - 1) % mod) % mod;
		}
	} 
	return dp[pos][f1][f2] = res;
}
int main(){
	ios::sync_with_stdio(false);
	cin.tie(0), cout.tie(0);
	cin >> B;
	T[0] = 1;
	for(int i = 1; i <= maxn; i++) T[i] = ((LL)T[i - 1] * B % mod + 1) % mod;
	int n;
	cin >> n;
	n--;
	for(int i = n; i >= 0; i--) cin >> a[i];
	if(a[n]){
		for(int i = 0; i <= n; i++){
			if(a[i]){
				a[i]--;
				break;
			}
			a[i] = B - 1;
		}
		if(!a[n]) n--;
		ans -= dfs(n, 1, 1).s2;
	}
	cin >> n;
	n--;
	for(int i = n; i >= 0; i--) cin >> a[i];
	memset(vis, 0, sizeof(vis));
//	debug(ans);
	ans += dfs(n, 1, 1).s2;
	cout << (ans % mod + mod) % mod << '\n';
	return 0;
}
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值