LZUoj 回文整数求和 (数位dp)

题目链接:http://oj.lovelyanqi.com/problem/L04 K

想知道 \([0, x]\) 之内有多少个回文数,

假设 \(x\)\(c\) 位,我们可以先预处理出前 \(c - 1\) 位内 \((99....99)\) 的答案
然后再加上 \(10^{c-1}\)\(x\) 内的答案即可

考虑如何统计,需要对 \(x\) 的数位分奇偶性讨论

如果数位是奇数
例如 \(12345\) ,则前半部分为 \([10,12]\)
以第三位的数为中间点,如果前半部分 \(left_x\)\([10,11]\), 则中间数位可以为 \([0,9]\) 的任意数,
如果 \(left_x\)\(12\), 则中间数位只能为 \([0,3]\) 之间的数,否则就会大于 \(x\)

偶数的情况类似,不过没有了中间点

重头戏来了

如果为奇数

\(lx\)为前半部分的数, \(M\) 为中间位置的数, \(rx\)\(lx\) 倒置过来的数 \((12345 -> 54321)\), 记为 \(rx = reverse(lx)\)

先考虑 \(lx < left_x\) 的情况:
当前半部分小于 \(left_x\) 时,形如 \(lx M rx\) 的都为合法答案,其中 \(lx < left_x, M 属于 [0,9], rx = reverse(lx)\)

分别考虑 \(lx , M ,rx\) 的贡献,\(lx\) 贡献了 \(sumlx * 10^{c / 2}\),
\(rsum\) 表示所有合法 \(rx\) 的和,则 \(sumx\) 贡献了 $ 10 $ 次 \(M = [0,9]\)都贡献一次,即为 $ M * rsum $.

再考虑 \(lx = left_x\) 的情况:
\(M < M_x\) 时,\(rsum_x\) 贡献了 \(M\) 次,
\(M = M_x\) 时,需要比较 \(reverse(rx)\)\(lx\) 的大小关系,
如果 \(reverse(rx) > lx\), 则 \(left_x M_x reverse(left_x)\) 为合法回文数,需要加上其自身的贡献

否则不合法,不需要算贡献

偶数的情况类似,不过要比奇数的情况简单很多,可以自己尝试推一下

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<cmath>
#include<stack>
#include<queue>
using namespace std;
typedef long long ll;

const int maxn = 1000010;

int T, a, b;
ll sum[maxn], rsum[10010], po[15];
ll S[15] = {1, 45, 540, 50040, 545040, 50045040, 545045040, 50045045040, 545045045040, 50045045045040};

int f[15];

int rev(int x){
	int res = 0;
	int cnt = 0;
	int tmp = x;
	while(tmp){
		f[++cnt] = tmp % 10;
		tmp /= 10;
	}
	for(int i = 1 ; i <= cnt ; ++i){
		res += f[i] * po[cnt - i];
	}
	
	return res;
}

ll calc(int x){
	ll res = 0;
	int cnt = 0;
	int tmp = x;
	while(tmp){
		++cnt;
		tmp /= 10;
	}
	
	res = 1ll * (po[cnt - 1] + x - 1) * (x - po[cnt - 1]) / 2; 
	
	return res;
}

void init(){
	for(int i = 1 ; i <= 9999 ; ++i){
		rsum[i] = rsum[i - 1] + rev(i);
	}
	for(int i = 1 ; i <= 99999 ; ++i){
		sum[i] = calc(i);
	}
}

ll solve(int x){
	if(x == -1) return 0;
	ll res = 0;
	
	int cnt = 0;
	int tmp = x;
	while(tmp){
		++cnt;
		tmp /= 10;
	}
	
	int lx = x / po[cnt / 2], rx = x % po[cnt / 2], rlx = rev(lx) % po[cnt / 2] ;
	res = S[cnt - 1];
	res += 1ll * sum[lx] * po[cnt / 2];
	if(cnt % 2 == 0){
		res += rsum[lx - 1] - rsum[po[cnt / 2 - 1] - 1];
		
		if(rlx <= rx){
			res += 1ll * lx * po[cnt / 2] + rlx;
		}
	} else{
		int plx = lx / 10, rem = lx % 10;
		
		res += 1ll * 10 * ( rsum[plx - 1] - rsum[po[cnt / 2 - 1] - 1]);
		
		if(rev(plx) <= rx){
			res += 1ll * rem * ( rsum[plx] - rsum[plx - 1]);
			res += 1ll * lx * po[cnt / 2];
			res += rsum[plx] - rsum[plx - 1]; // 给新加的加上 
		} else{
			res += 1ll * rem * ( rsum[plx] - rsum[plx - 1]);
		}
	}
	
	return res;
}

ll read(){ ll s=0,f=1; char ch=getchar(); while(ch<'0' || ch>'9'){ if(ch=='-') f=-1; ch=getchar(); } while(ch>='0' && ch<='9'){ s=s*10+ch-'0'; ch=getchar(); } return s*f; }

int main(){
	po[0] = 1;
	for(int i = 1 ; i <= 10 ; ++i) po[i] = 1ll * po[i - 1] * 10;
	init();
	
	T = read();
	while(T--){
		a = read(), b = read();
		if(a == 1000000000) --a;
		if(b == 1000000000) --b;
		ll ansa = 0, ansb = 0 ;
		if(a - 1 < 10){
			for(int i = 1 ; i <= a - 1 ; ++i) ansa += i;
		} else{
			ansa = solve(a - 1);
		}
		if(b < 10){
			for(int i = 1 ; i <= b ; ++i) ansb += i;
		} else{
			ansb = solve(b);
		}
		printf("%lld\n", ansb - ansa);
	}
	return 0;
}
posted @ 2020-11-22 21:44  Tartarus_li  阅读(228)  评论(0编辑  收藏  举报