【BZOJ3326】数数(SCOI2013)-数位DP

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Maxwei_wzj/article/details/83000384

测试地址:数数
题目大意: 给定 L , R L,R 两个 1 0 5 10^5 位内的 B ( 1 0 5 ) B(\le 10^5) 进制数, L R L\le R ,对区间 [ L , R ] [L,R] 内的所有数 x x ,累加 x x 中所有子串表示的数字的和(如 123 123 ,应该累加 123 + 12 + 23 + 1 + 2 + 3 123+12+23+1+2+3 到答案中,注意不应该包含前导零),求最终答案(用 10 10 进制表示)。
做法: 本题需要用到数位DP。
神题。虽然一眼能看出数位DP,但具体的转移式子还是想错了好多回,这次终于写对了。
首先我们来看,对于一个 l e n len 位的数 x x ,在它末尾加一位数 s s ,会对这个数的所有子串表示的数字和有什么影响(以下简写成 s u m ( x ) sum(x) )。令 s u f ( x ) suf(x) 表示数 x x 所有后缀表示的数字和,那么有:
s u f ( x n e w ) = s u f ( x l a s t ) B + s ( l e n + 1 ) suf(x_{new})=suf(x_{last})\cdot B+s\cdot (len+1)
s u m ( x n e w ) = s u m ( x l a s t ) + s u f ( x n e w ) sum(x_{new})=sum(x_{last})+suf(x_{new})
根据这个为基础,我们就能思考数位DP的转移了。
根据套路,首先把问题转化为:用小于等于 R R 的所有数的贡献,减去小于等于 L 1 L-1 的所有数的贡献,于是现在我们考虑求小于等于某个数 T T 时的贡献。
从高位向低位枚举,令 S u m ( i , 0 / 1 ) Sum(i,0/1) 表示前 i i 位中,不卡/卡上界的所有数的 s u m ( x ) sum(x) 的和, S u f ( i , 0 / 1 ) Suf(i,0/1) 表示前 i i 位中,不卡/卡上界的所有数的 s u f ( x ) suf(x) 的和。先分析具体的转移过程:
i 1 i-1 位的数不卡上界时,第 i i 位可以填 0 0 ~ B 1 B-1 内所有的数,并且新的数都不卡上界;
i 1 i-1 位的数卡上界时,第 i i 位可以填 0 0 ~ T [ i ] T[i] 。当填 0 0 ~ T [ i ] 1 T[i]-1 时,新的数不卡上界,当填 T [ i ] T[i] 时新的数卡上界。
于是我们先考虑 S u f Suf 的转移。首先,卡上界的情况应该很好转移了,实际上就是求上界的 s u f suf 值。主要是不卡上界的情况比较复杂。
首先考虑不卡上界转移到不卡上界的情况。根据上面的转移式子 s u f ( x n e w ) = s u f ( x l a s t ) B + s ( l e n + 1 ) suf(x_{new})=suf(x_{last})\cdot B+s\cdot (len+1) ,我们这样考虑:首先枚举 s s 0 0 B 1 B-1 ,对于每一个 s s ,再枚举可转移的 x l a s t x_{last} ,把贡献累加起来。于是一个 s s 对整个 S u f Suf 的贡献是: B s u f ( x l a s t ) + s ( l e n ( x l a s t ) + 1 ) B\cdot \sum suf(x_{last})+s\cdot \sum (len(x_{last})+1) ,那么对于所有 s s ,对 S u f Suf 的贡献就是: B B s u f ( x l a s t ) + B ( B 1 ) 2 ( l e n ( x l a s t ) + 1 ) B\cdot B\cdot \sum suf(x_{last})+\frac{B(B-1)}{2}\cdot \sum (len(x_{last})+1) 。其中 s u f ( x l a s t ) \sum suf(x_{last}) 就是 S u f ( i 1 , 0 ) Suf(i-1,0) ,而 ( l e n ( x l a s t ) + 1 ) \sum (len(x_{last})+1) 需要斟酌一下。这个和式是在求,对于所有可转移的 x l a s t x_{last} (包括 0 0 ),累加它们的位数 + 1 +1 (把 0 0 的位数看做 0 0 )。观察规律,我们发现: 1 1 1 1 个, 2 2 B 1 B-1 个, 3 3 ( B 1 ) B (B-1)\cdot B 个, 4 4 ( B 1 ) B 2 (B-1)\cdot B^2 个… i 1 i-1 ( B 1 ) B i 3 (B-1)\cdot B^{i-3} 个, i i n u m B i 2 num-B^{i-2} 个, n u m num 表示 T T 的前 i 1 i-1 位组成的前缀。那么前面的有规律的部分可以递推维护,而 n u m num 显然也可以递推维护,所以我们就可以每次 O ( 1 ) O(1) 地进行这个转移了。
接下来考虑卡上界转移到不卡上界的情况。这种情况下,能转移到不卡上界的情况, s s 必须是 0 0 ~ T [ i ] 1 T[i]-1 ,而且因为这种情况中可转移的 x l a s t x_{last} 只有一个,而且 l e n ( x l a s t ) len(x_{last}) 就是 i 1 i-1 ,因此就比上面的情况简单很多了,总贡献应该为 S u f ( i 1 , 1 ) B + T [ i ] ( T [ i ] 1 ) 2 i Suf(i-1,1)\cdot B+\frac{T[i](T[i]-1)}{2}\cdot i
那么 S u f Suf 的转移讨论完了,接下来讨论 S u m Sum 的转移。 S u m ( i , 1 ) Sum(i,1) 就是求上界的 s u m sum ,而 S u m ( i , 0 ) Sum(i,0) 也利用上面的思考方式,先考虑从不卡上界转移的情况,因为有 B B 种转移,所以 S u m ( i 1 , 0 ) Sum(i-1,0) 就产生了 B B 次的贡献。再考虑从卡上界转移的情况,因为有 T [ i ] T[i] 种转移,所以 S u m ( i 1 , 1 ) Sum(i-1,1) 就产生了 T [ i ] T[i] 次的贡献。再加上所有不卡上界数的后缀产生的贡献,即 S u f ( i , 0 ) Suf(i,0) ,就可以计算出 S u m ( i , 0 ) Sum(i,0) 了。
至此,经过漫长的讨论,我们得到了一个 O ( n ) O(n) 的数位DP,完美地解决了这一道题。
以下是本人代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=20130427;
int n;
ll s[100010],B,sum[100010][2],suf[100010][2];
ll pw[100010],num[100010],sumnum[100010]={0};

ll solve()
{
	sum[0][0]=sum[0][1]=suf[0][0]=suf[0][1]=num[0]=0;
	for(int i=1;i<=n;i++)
	{
		if (i>2) sumnum[i]=(sumnum[i-1]+(ll)(i-1)*(B-1ll)%mod*pw[i-3]%mod)%mod;
		num[i]=(num[i-1]*B%mod+s[i])%mod;
		ll tmp=0;
		if (i>1) tmp=(sumnum[i]+1ll+(num[i-1]-pw[i-2]+mod)%mod*(ll)i%mod)%mod;
		suf[i][1]=(suf[i-1][1]*B%mod+s[i]*(ll)i%mod)%mod;
		suf[i][0]=(suf[i-1][0]*B%mod*B%mod+B*(B-1ll)/2ll%mod*tmp%mod)%mod;
		suf[i][0]=(suf[i][0]+suf[i-1][1]*B%mod*s[i]%mod+s[i]*(s[i]-1ll)/2ll%mod*(ll)i%mod)%mod;
		sum[i][1]=(sum[i-1][1]+suf[i][1])%mod;
		sum[i][0]=(sum[i-1][0]*B%mod+suf[i][0])%mod;
		sum[i][0]=(sum[i][0]+sum[i-1][1]*s[i]%mod)%mod;
	}
	return (sum[n][0]+sum[n][1])%mod;
}

int main()
{
	ll ans;
	
	scanf("%lld",&B);
	scanf("%d",&n);
	pw[0]=1;
	for(int i=1;i<=n;i++)
	{
		scanf("%lld",&s[i]);
		pw[i]=pw[i-1]*B%mod;
	}
	ans=(mod-solve()+sum[n][1])%mod;
	
	scanf("%d",&n);
	pw[0]=1;
	for(int i=1;i<=n;i++)
	{
		scanf("%lld",&s[i]);
		pw[i]=pw[i-1]*B%mod;
	}
	ans=(ans+solve())%mod;
	
	printf("%lld",ans);
	
	return 0;
}

猜你喜欢

转载自blog.csdn.net/Maxwei_wzj/article/details/83000384