数数

这一道题给我们最大的启示就是一定要学会固定数字!下面要求的数组,如果按照数位DP的套路,从小到大考虑数位以及数位上的数字,是很容易想到需要求这些数组的

\(Pow[i]=B^i\),\(f[i]=\sum_{k=0}^{i}{B^k}\)

\(h[i]\)表示所有\(i\)位数字的所有前缀子串的和

比如\(123456\)一共有\(6\)位,他的所有前缀子串为\(1,12,123,1234,12345,123456\),他的所有前缀子串和就是这六个数加起来,那么\(h[6]\)就表示\(000000,000001,……,999999\)所有这些数字的前缀子串和的和

我们求\(h[i]\)时,考虑最高位填什么。此时最高位在变换(可以从\(0\)一直到\(B-1\)),而剩下的数字也在变换(可以从\(\underbrace{00...0}_{\text{i-1个0}}\)一直到\(\underbrace{99...9}_{\text{i-1个9}}\)),我们就不好讨论,所以先固定假设我们已经选择了最高位的数字,这个数字为\(j\),剩下位的数字为\(\overline{abc...}\)

那么此时的所以前缀为\(\overline{j},\overline{ja},\overline{jab}...\),他们分别可以表示成\(j,j\cdot Pow[1]+a,j\cdot Pow[2]+a\cdot Pow[1]+b...\),所以这一个已经固定的数字的所有前缀和就是\(j\cdot f[i-1]\)+(\(\overline{abc...}\)这个数字的所有前缀子串和)

此时我们再假设\(j\)固定,\(\overline{abc...}\)在变化,再求一个和(即当最高位为\(j\)时,对\(h[i]\)的贡献),则为\(j\cdot f[i-1]\cdot Pow[i-1]+h[i-1]\)(注意一共有\(Pow[i-1]\)个数)

然后我们再假设\(j\)也在变换,再求一个和,则有$$h[i]=((\sum_{j=0}^{B-1}j)\cdot f[i-1]\cdot Pow[i-1])+B\cdot h[i-1]$$

以上过程完全可以先固定\(\overline{abc...}\),假设\(j\)在变化,然后假设\(\overline{abc...}\)在变化,答案是一模一样的

\(g[i]\)表示所有\(i\)位数字的所有子串的和,用以上同样的方法可以得出$$g[i]=B\cdot g[i-1]+h[i]$$

然后试填的过程见以下代码

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=1e5+10;
const ll p=20130427;
int n,a[N];
ll f[N],g[N],h[N],Pow[N],B;
void prework()
{
	f[0]=1;
	Pow[0]=1;
	for(int i=1;i<=N-10;i++)
	{
		Pow[i]=Pow[i-1]*B%p;
		f[i]=(f[i-1]+Pow[i])%p;
		h[i]=(((B-1)*B>>1)%p*f[i-1]%p*Pow[i-1]%p+B*h[i-1]%p)%p;
		g[i]=(B*g[i-1]%p+h[i])%p;
	}
}
ll work()
{
	ll sum=0,val=0,res=0;
	//sum表示已经填了的数字的所有子串的和
	//val表示已经填了的数字的所有前缀子串的和
	//rse表示结果 
	for(int i=n;i>=2;i--) 
	for(int j=1;j<B;j++)
	{
		res=(res+g[n-i]+h[n-i])%p;
		res=(res+(j*Pow[n-i]%p*f[n-i]%p))%p;
	}
	//这个循环是在填写只有1位,2位...i-1位的情况
	//注意不能直接填写最高位的0
	//因为此时会导致重复计算
	//比如n=3,数字为095
	//那么本来应该只算9,5,95这三个子串的
	//但如果直接从最高位的0开始算
	//就会算成0,9,5,09,95,095
	//显然重复了 
	for(int i=1;i<a[1];i++)
	{
		res=(res+g[n-1])%p;
		res=(res+(i*Pow[n-1]%p*f[n-1]%p+h[n-1])%p)%p;
	}
	//考虑最高位的写法 
	val=a[1],sum=a[1];
	for(int i=2;i<=n;i++)
	{
		for(int j=0;j<a[i];j++)
		{
			res=(res+g[n-i])%p;
			ll temp=(val*B%p+(ll)j*i%p)%p;
			res=(res+(temp*Pow[n-i]%p*f[n-i]%p+i*h[n-i])%p)%p;
			res=(res+sum*Pow[n-i]%p)%p;
		}
		val=(val*B%p+(ll)a[i]*i%p)%p;
		sum=(sum+val)%p;
	}
	//可以用博客讲的方法推一下上面代码的式子 
	return (res+sum)%p;
	//注意还要在加上sum
	//因为要计算这个数本身的贡献 
}
int main()
{
	scanf("%lld",&B);
	prework();
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	int j=n;
	while(!a[j]) 
	{
		a[j]=B-1;
		j--;
	}
	a[j]--;
	if(j==1&&!a[j]) 
	{
		n--;
		for(int i=1;i<=n;i++) a[i]=B-1;
	}
	ll res1=work();
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	ll res2=work();
	printf("%lld",(res2-res1+p)%p);
    return 0;
}

以上代码肯定会超时,但是这个优化太简单了,具体见洛谷的提交

update 2024.9.17

重新做一遍,做出来了

其实我们走起来应该想的是求\(g\)这个数组,枚举最高位数字,我们就要去统计以\(j\)开头的所有子串的和;当然其实不一定想得到\(h\)这个数组,我们考虑统计长度刚好为\(l\)的所有\(B\)进制数的和,不难得到为\(\frac{B^l(B^l-1)}{2}\);然后考虑\(j\)后面的数字的长度为\(k\)(也就是现在我们在统计以\(j\)开头长度为\(k+1\)的前缀的和),再对所有\(k\)求和之后,可以得到

\[B\times h[i-1]=\frac{B^i(\frac{(B^{i-1}-1)B}{B-1}-i+1)}{2} \]

但是注意,这道题目的模数不是质数,搞这个搞了好久。。纯诈骗,快速幂的幂的值就不是\(p-2\)了而是\(\phi(p)-1\)

posted @ 2023-10-31 15:00  最爱丁珰  阅读(13)  评论(0)    收藏  举报