6361. 【NOIP2019模拟2019.9.18】鲳数

题目

题目大意

给你一个区间 [ l , r ] [l,r] [l,r],求这个区间内每个整数的十进制上从高位到低位的逆序对个数之和。


思考历程

一开始就知道这是个数位DP……
结果一直都没有调出来,心态崩了……


正解

先讲讲我的SB做法。
先设 f i f_i fi表示压着第 i i i位(从低位到高位,从 0 0 0开始)的贡献。
于是转移就是这样:

  1. 计算第 i i i位的贡献。这一位的贡献可能有点难计算,所以我预处理了一个 h i , j , 0 / 1 h_{i,j,0/1} hi,j,0/1表示是否压着 i i i位, 0 0 0 i i i位对 j j j的贡献( j j j i i i位之后的某个在 [ 0 , 9 ] [0,9] [0,9]之间的数)。那么这个东西就是 h i , a i h_{i,a_i} hi,ai
  2. i − 1 i-1 i1位转移过来,其中 i − 1 i-1 i1位也被压着。显然是 f i − 1 f_{i-1} fi1
  3. i − 1 i-1 i1位转移过来,其中 i − 1 i-1 i1位不被压着。枚举这一位选择 x x x,贡献就是 ∑ x = 0 a i − 1 − 1 ∣ x < j ∣ ∗ 1 0 i − 1 + g i − 2 + ( i − 1 ) ∗ 1 0 i − 2 x \sum_{x=0}^{a_{i-1}-1}|x<j|*10^{i-1}+g_{i-2}+(i-1)*10^{i-2}x x=0ai11x<j10i1+gi2+(i1)10i2x g i g_i gi表示 0 0 0位到 i i i位任意选的方案数。这条式子可以化简,这里就不打出来了。

先考虑 g g g怎么转移,显然是从前面转移过来并且加上这一位的贡献。
也就是 g i = 10 ∗ g i − 1 + ( 0 + 9 ) ∗ 10 2 i ∗ 1 0 i g_i=10*g_{i-1}+\frac{(0+9)*10}{2}i*10^i gi=10gi1+2(0+9)10i10i

再考虑 h h h的转移。
h i , j , 0 = j ∗ 1 0 i + 10 ∗ h i − 1 , j , 0 h_{i,j,0}=j*10^i+10*h_{i-1,j,0} hi,j,0=j10i+10hi1,j,0
h i , j , 1 h_{i,j,1} hi,j,1的转移相对复杂一些。和 f f f的转移有点类似。
n u m num num 0 0 0 i − 1 i-1 i1位形成的十进制数,也就是整个数模 1 0 i 10^i 10i
h i , j , 1 = ∣ a i < j ∣ ∗ ( n u m + 1 ) + h i − 1 , j , 1 + ( ∑ x = 0 a i − 1 − 1 ∣ x < j ∣ ∗ 1 0 i − 1 + h i − 2 , j , 0 ) h_{i,j,1}=|a_i<j|*(num+1)+h_{i-1,j,1}+(\sum_{x=0}^{a_{i-1}-1}|x<j|*10^{i-1}+h_{i-2,j,0}) hi,j,1=ai<j(num+1)+hi1,j,1+(x=0ai11x<j10i1+hi2,j,0)
同样也可以化简一下。

化简后,很容易发现这样转移的时间复杂度是 O ( 10 n ) O(10n) O(10n)的。

另外有个可能比较简单地比较高级的做法:
从高位到低位枚举一个 i i i,表示 i i i位压着上限,然后记下此时的贡献。
那么贡献有三种:

  1. 高于 i i i的位和 i i i的贡献。由于 i i i是压着上限的,所以前面所有数字的出现次数可以用一个桶记录下来。
  2. i i i位和低于 i i i位的贡献。我们强制后面一位没有压着上限,因为后面那一位压着上限的方案数将会在后面计算到。枚举后面的一位,再后面的就可以随便选。
  3. 高于 i i i位和低于 i i i位的贡献。这个计算也跟上一个差不多。

然后就没了。


代码

这题是在是太恶心了……细节奇多无比……

using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cassert>
#define mo 998244353
#define LEN 500010
#define ll long long
char str[LEN];
int n,m,L[LEN],R[LEN];
inline void read(int a[],int &n){
	scanf("%s",str);
	n=strlen(str);
	for (int i=0;i<n;++i)
		a[i]=str[n-i-1]-'0';
	a[n]=0;
}
ll _pow10[LEN],*pow10=_pow10+1;
ll f[LEN],g[LEN],h[LEN][10][2];
inline ll get(int a[],int n){
	if (n<=0)
		return 0;
	g[0]=0;
	for (int i=1;i<=n;++i)
		g[i]=(g[i-1]*10+45*i*pow10[i-1])%mo;
	for (int j=0;j<=9;++j)
		h[0][j][0]=j,h[0][j][1]=(a[0]<j);
	for (int i=1,num=a[0];i<=n;num=(num+pow10[i]*a[i])%mo,++i)
		for (int j=0;j<=9;++j){
			h[i][j][0]=(h[i-1][j][0]*10+j*pow10[i])%mo;
			int tmp=min(a[i-1],j);
			h[i][j][1]=((a[i]<j?num+1:0)+h[i-1][j][1]+(i-2>=0?h[i-2][j][0]*a[i-1]:0)+tmp*pow10[i-1])%mo;
		}
	f[0]=0,f[1]=min(a[0]+1,a[1]);
	for (int i=2;i<=n;++i)
		f[i]=(h[i][a[i]][1]+f[i-1]+a[i-1]*g[i-2]+(i-1)*pow10[i-2]*((a[i-1]-1)*a[i-1]>>1))%mo;
	return f[n];
}
int main(){
	freopen("pair.in","r",stdin);
	freopen("pair.out","w",stdout);
	pow10[0]=1;
	for (int i=1;i<=500001;++i)
		pow10[i]=pow10[i-1]*10%mo;
	int T,ty;
	scanf("%d%d",&T,&ty);
	while (T--){
		read(L,n),read(R,m);
		L[0]--;
		for (int i=0;L[i]<0;++i)
			L[i+1]--,L[i]+=10;
		if (!L[n-1])
			n--;
		printf("%lld\n",(get(R,m)-get(L,n)+mo)%mo);
	}
	return 0;
}

总结

其实这题思维上是不难的……
只是过程实在太烦……

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值