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

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值