【BZOJ3326】数数(SCOI2013)-数位DP
测试地址:数数
题目大意: 给定两个位内的进制数,,对区间内的所有数,累加中所有子串表示的数字的和(如,应该累加到答案中,注意不应该包含前导零),求最终答案(用进制表示)。
做法: 本题需要用到数位DP。
神题。虽然一眼能看出数位DP,但具体的转移式子还是想错了好多回,这次终于写对了。
首先我们来看,对于一个位的数,在它末尾加一位数,会对这个数的所有子串表示的数字和有什么影响(以下简写成)。令表示数所有后缀表示的数字和,那么有:
根据这个为基础,我们就能思考数位DP的转移了。
根据套路,首先把问题转化为:用小于等于的所有数的贡献,减去小于等于的所有数的贡献,于是现在我们考虑求小于等于某个数时的贡献。
从高位向低位枚举,令表示前位中,不卡/卡上界的所有数的的和,表示前位中,不卡/卡上界的所有数的的和。先分析具体的转移过程:
前位的数不卡上界时,第位可以填 ~ 内所有的数,并且新的数都不卡上界;
前位的数卡上界时,第位可以填 ~ 。当填 ~ 时,新的数不卡上界,当填时新的数卡上界。
于是我们先考虑的转移。首先,卡上界的情况应该很好转移了,实际上就是求上界的值。主要是不卡上界的情况比较复杂。
首先考虑不卡上界转移到不卡上界的情况。根据上面的转移式子,我们这样考虑:首先枚举从到,对于每一个,再枚举可转移的,把贡献累加起来。于是一个对整个的贡献是:,那么对于所有,对的贡献就是:。其中就是,而需要斟酌一下。这个和式是在求,对于所有可转移的(包括),累加它们的位数(把的位数看做)。观察规律,我们发现:有个,有个,有个,有个…有个,有个,表示的前位组成的前缀。那么前面的有规律的部分可以递推维护,而显然也可以递推维护,所以我们就可以每次地进行这个转移了。
接下来考虑卡上界转移到不卡上界的情况。这种情况下,能转移到不卡上界的情况,必须是 ~ ,而且因为这种情况中可转移的只有一个,而且就是,因此就比上面的情况简单很多了,总贡献应该为。
那么的转移讨论完了,接下来讨论的转移。就是求上界的,而也利用上面的思考方式,先考虑从不卡上界转移的情况,因为有种转移,所以就产生了次的贡献。再考虑从卡上界转移的情况,因为有种转移,所以就产生了次的贡献。再加上所有不卡上界数的后缀产生的贡献,即,就可以计算出了。
至此,经过漫长的讨论,我们得到了一个的数位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;
}