链接
https://www.luogu.org/problem/show?pid=3281
题解
不太好想。。
如果我们直接记录字串的和,转移会不太好想(反正我是没想出来)。考虑确定子串的因素可以是开头和长度,所以记录每个开头处的所有前缀的和,统计答案时枚举开头即可。
f[i][0/1]表示长度为i的数的所有前缀的和(允许前导0的出现)。
假设f[i-1][0]已经求出,我们枚举最高位的数字
now∈[0,B−1]
,我们要求的是所有以
now
为开头的那些前缀数字的和,既然最高位已经确定,就是说后面的位要从0循环到
Bi−1−1
,一共
Bi−1
个数,每个数中,
now
都出现了,且每次都作为
i
个串的开头,对答案的贡献分别是
其中 num[i] 表示输入的i这位上的数字, n[i] 表示后 i 位组成的数字,
前面的随便一推就推出来了,难点在于怎样统计答案。
考虑答案中允许有前导0出现,但是出现前导0串的前提是这这整个串不包含前导0。比如103,需要统计03这样的串,但是003中,不能统计003和03的和。
设
根据最初的思想,要把每个位置开头的串的和全部统计进ans里。那么枚举开头位置i,明显地,当更高位上全是0的时候,当前位置必须从1开始,更高位上是1到 m[i+1]−1 的时候,当前位置可以无限制,用 f[i][0] ,当更高位上达到 m[i+1] 时,当前必须用 f[i][1] 。首先第一种情形可以舍去,因为一串连续的0之后必定有一个非0,而这成了子问题,可以只考虑前面的数大于0的情况。考虑当前面是 [1,m[i+1]−1] 时,当前位对答案的贡献都是 f[i][0] ,一共贡献 m[i+1]−1 次(要和0取max),前面是m[i+1]时,当前位对答案的贡献是 f[i][1] 。最终,
预处理m数组,统计答案是O(N)的。
这应该是我独立想出来的较麻烦的题目了吧。
代码
#include <cstdio>
#include <algorithm>
#define maxn 300000
#define mod 20130427
#define ll long long
using namespace std;
ll B, b[maxn], s[maxn];
void init()
{
ll i;
b[0]=s[0]=1;
for(i=1;i<=200000;i++)b[i]=b[i-1]*B%mod, s[i]=(s[i-1]+b[i])%mod;
}
ll dp(ll *num)
{
ll N=*num, i, n[maxn]={0}, f[maxn][2]={0}, ans=0, m[maxn]={0};
for(i=1;i<=N;i++)n[i]=(num[i]*b[i-1]+n[i-1])%mod;
for(i=N;i>=1;i--)m[i]=(m[i+1]*B+num[i])%mod;
for(i=1;i<=N;i++)
{
f[i][0]=(B*f[i-1][0]%mod+B*(B-1)/2%mod*b[i-1]%mod*s[i-1])%mod;
f[i][1]=(num[i]*f[i-1][0]%mod+num[i]*(num[i]-1)/2%mod*b[i-1]%mod*s[i-1])%mod;
f[i][1]=(f[i][1]+f[i-1][1]+num[i]*(n[i-1]+1)%mod*s[i-1])%mod;
ans=(ans+max((ll)0,m[i+1]-1)*f[i][0]%mod+f[i][1])%mod;
ans%=mod;
}
return ans;
}
void reverse(ll *num)
{for(ll i=1;i<*num-i+1;i++)swap(num[i],num[*num-i+1]);}
void dec(ll *num)
{
num[1]--;
for(ll i=1;i<=*num;i++)if(num[i]<0)num[i]+=B, num[i+1]--;
if(num[*num]==0)--*num;
}
int main()
{
ll num[maxn], i, ans;
scanf("%lld",&B);
init();
scanf("%lld",num);
for(i=1;i<=*num;i++)scanf("%lld",num+i);
reverse(num), dec(num);
ans=-dp(num);
scanf("%lld",num);
for(i=1;i<=*num;i++)scanf("%lld",num+i);
reverse(num), ans+=dp(num);
printf("%lld",(ans+mod)%mod);
return 0;
}