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

测试地址:数数
题目大意: 给定L,RL,R两个10510^5位内的B(105)B(\le 10^5)进制数,LRL\le R,对区间[L,R][L,R]内的所有数xx,累加xx中所有子串表示的数字的和(如123123,应该累加123+12+23+1+2+3123+12+23+1+2+3到答案中,注意不应该包含前导零),求最终答案(用1010进制表示)。
做法: 本题需要用到数位DP。
神题。虽然一眼能看出数位DP,但具体的转移式子还是想错了好多回,这次终于写对了。
首先我们来看,对于一个lenlen位的数xx,在它末尾加一位数ss,会对这个数的所有子串表示的数字和有什么影响(以下简写成sum(x)sum(x))。令suf(x)suf(x)表示数xx所有后缀表示的数字和,那么有:
suf(xnew)=suf(xlast)B+s(len+1)suf(x_{new})=suf(x_{last})\cdot B+s\cdot (len+1)
sum(xnew)=sum(xlast)+suf(xnew)sum(x_{new})=sum(x_{last})+suf(x_{new})
根据这个为基础,我们就能思考数位DP的转移了。
根据套路,首先把问题转化为:用小于等于RR的所有数的贡献,减去小于等于L1L-1的所有数的贡献,于是现在我们考虑求小于等于某个数TT时的贡献。
从高位向低位枚举,令Sum(i,0/1)Sum(i,0/1)表示前ii位中,不卡/卡上界的所有数的sum(x)sum(x)的和,Suf(i,0/1)Suf(i,0/1)表示前ii位中,不卡/卡上界的所有数的suf(x)suf(x)的和。先分析具体的转移过程:
i1i-1位的数不卡上界时,第ii位可以填00 ~ B1B-1内所有的数,并且新的数都不卡上界;
i1i-1位的数卡上界时,第ii位可以填00 ~ T[i]T[i]。当填00 ~ T[i]1T[i]-1时,新的数不卡上界,当填T[i]T[i]时新的数卡上界。
于是我们先考虑SufSuf的转移。首先,卡上界的情况应该很好转移了,实际上就是求上界的sufsuf值。主要是不卡上界的情况比较复杂。
首先考虑不卡上界转移到不卡上界的情况。根据上面的转移式子suf(xnew)=suf(xlast)B+s(len+1)suf(x_{new})=suf(x_{last})\cdot B+s\cdot (len+1),我们这样考虑:首先枚举ss00B1B-1,对于每一个ss,再枚举可转移的xlastx_{last},把贡献累加起来。于是一个ss对整个SufSuf的贡献是:Bsuf(xlast)+s(len(xlast)+1)B\cdot \sum suf(x_{last})+s\cdot \sum (len(x_{last})+1),那么对于所有ss,对SufSuf的贡献就是:BBsuf(xlast)+B(B1)2(len(xlast)+1)B\cdot B\cdot \sum suf(x_{last})+\frac{B(B-1)}{2}\cdot \sum (len(x_{last})+1)。其中suf(xlast)\sum suf(x_{last})就是Suf(i1,0)Suf(i-1,0),而(len(xlast)+1)\sum (len(x_{last})+1)需要斟酌一下。这个和式是在求,对于所有可转移的xlastx_{last}(包括00),累加它们的位数+1+1(把00的位数看做00)。观察规律,我们发现:1111个,22B1B-1个,33(B1)B(B-1)\cdot B个,44(B1)B2(B-1)\cdot B^2个…i1i-1(B1)Bi3(B-1)\cdot B^{i-3}个,iinumBi2num-B^{i-2}个,numnum表示TT的前i1i-1位组成的前缀。那么前面的有规律的部分可以递推维护,而numnum显然也可以递推维护,所以我们就可以每次O(1)O(1)地进行这个转移了。
接下来考虑卡上界转移到不卡上界的情况。这种情况下,能转移到不卡上界的情况,ss必须是00 ~ T[i]1T[i]-1,而且因为这种情况中可转移的xlastx_{last}只有一个,而且len(xlast)len(x_{last})就是i1i-1,因此就比上面的情况简单很多了,总贡献应该为Suf(i1,1)B+T[i](T[i]1)2iSuf(i-1,1)\cdot B+\frac{T[i](T[i]-1)}{2}\cdot i
那么SufSuf的转移讨论完了,接下来讨论SumSum的转移。Sum(i,1)Sum(i,1)就是求上界的sumsum,而Sum(i,0)Sum(i,0)也利用上面的思考方式,先考虑从不卡上界转移的情况,因为有BB种转移,所以Sum(i1,0)Sum(i-1,0)就产生了BB次的贡献。再考虑从卡上界转移的情况,因为有T[i]T[i]种转移,所以Sum(i1,1)Sum(i-1,1)就产生了T[i]T[i]次的贡献。再加上所有不卡上界数的后缀产生的贡献,即Suf(i,0)Suf(i,0),就可以计算出Sum(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;
}
posted @ 2018-10-10 19:05  Maxwei_wzj  阅读(215)  评论(0编辑  收藏  举报