csp模拟 字符串问题【计数】【组合数学】

传送门

又是一个计数题,,

在n个数中间填加号,求所有方案的数字和。

下面提供两种解法,题解的和来自FSY的。

题解:

考虑每个区间对数的贡献,要么没有贡献,要么贡献是10的幂。

故我们可以枚举使得这个数的系数为10^i时,区间的个数。发现是个组合数。

发现这样枚举实际上是固定了该点所在区间的右端点,也就是说固定了一个加号的位置。设当前点为i,对i的贡献为10^j,则右端点为i+j。

发现i~i+j不能填加号,共j个位置。而i+j右边强制加了一个加号。所以剩下的加号方案为\binom{n-j-2}{m-1}

故贡献为\sum_{i=1}^n\sum_{j=0}^{n-i}10^j\binom{n-j-2}{m-1}a_i

发现复杂度n^2,考虑交换枚举顺序。如果先枚举j,即先枚举区间右端点到点i的距离。

\sum_{j=0}^n10^j\binom{n-j-2}{m-1}\sum_{i=1}^{n-j}a_i

于是等差数列求和即可。

 

方法2:

考虑枚举区间,计算区间被计算的次数。

在不考虑1和n两个端点的情况下,考虑一个区间[l,r]。

使得这个区间存在的条件是区间左右各有一个加号,区间内部没有加号。

所以加号的组合方案为\binom{n-len-2}{m-2}。故贡献为\sum_{i=1}^n\sum_{j=i}^nsum[i\to j]\binom{n-len-2}{m-2}

发现复杂度为n^2,发现组合数只跟区间长度len有关,考虑如何计算长度为len的所有区间和。

举例子时间:

如果有样例abcdefg

发现长度为1的和是a,b,c,d,e,f,g

长度为2的是ab,bc,cd,de,ef,fg

也就是10*(a+b+c+d+e+f)+(b+c+d+e+f+g)。

多举几个例子,你会发现,对于一个长度len,其答案为(len-1)的答案去掉最后一项*10,然后加上一个个位的后缀和。

所以维护后缀和然后该怎么计算怎么计算。

对于区间端点为1或者n的,自己手玩一下该怎么算,区别不大。

#include<bits/stdc++.h>
using namespace std;
#define in read()
#define int long long
int in{
	int cnt=0,f=1;char ch=0;
	while(!isdigit(ch)){
		ch=getchar();if(ch=='-')f=-1;
	}
	while(isdigit(ch)){
		cnt=cnt*10+ch-48;ch=getchar();
	}return cnt*f;
}
int fac[1000003],ifac[1000003];
int sum[1000003];
int suml[1000003];
int n,m;char ch[1000003];
const int mod=998244353;
int ksm(int a,int b){
	int gu=1;
	while(b){
		if(b&1)gu=gu*a%mod;a=a*a%mod;b>>=1;
	}return gu;
}
int jz[1000003];
int sumsuf[1000003];
int tem;
signed main(){
	n=in;m=in;fac[0]=ifac[0]=1;jz[0]=1;
	scanf("%s",ch+1);for(int i=1;i<=n;i++)jz[i]=jz[i-1]*10%mod;
	for(int i=1;i<=n;i++)fac[i]=fac[i-1]*i%mod;ifac[n]=ksm(fac[n],mod-2);
	for(int i=n-1;i>=1;i--)ifac[i]=ifac[i+1]*(i+1)%mod;
	for(int i=1;i<=n;i++)sum[i]=(sum[i-1]*10+ch[i]-'0')%mod;
	for(int i=n-1;i>=1;i--)sumsuf[i]=(sumsuf[i+1]+ch[i]-'0')%mod;
	for(int i=2;i<=n-1;i++)suml[1]+=ch[i]-'0',suml[1]%=mod;
	tem=suml[1]-ch[n-1]+'0';
	for(int i=2;i<n;i++){
		suml[i]=10*tem%mod+sumsuf[i+1];suml[i]%=mod;
		tem=suml[i]-(sum[n-1]-sum[n-i-1]*jz[i]%mod)%mod;tem=(tem%mod+mod)%mod;
	}`
//	for(int i=1;i<=n;i++){
//		cout<<suml[i]<<" ";
//	}cout<<endl;
	int ans=0;
	for(int i=1;i<=n;i++){
		int x=m-2,y=n-i-2;
		if(x>y||x<0||y<0)continue;
//		cout<<x<<" "<<y<<" "<<suml[i]<<" "<<fac[y]<<" "<<ifac[x]<<" "<<ifac[y-x]<<endl;
		ans=(ans+suml[i]*fac[y]%mod*ifac[x]%mod*ifac[y-x]%mod)%mod;
		//c/out<<ans<<endl;
	}//cout<<ans<<endl;
	for(int i=1;i<n;i++){
		int x=m-1,y=n-1-i;if(x<0||y<0| |y<x)continue;
		ans=(ans+sum[i]*fac[y]%mod*ifac[x]%mod*ifac[y-x]%mod)%mod;
	}
	//cout<<ans<<endl;
	for(int i=2;i<=n;i++){
		int x=m-1,y=i-2;if(x<0||y<0||y<x)continue;
//		cout<<i<<" "<<sum[n]<<" "<<sum[i-1]<<" "<<jz[n-i+1]<<" "<<sum[n]-sum[i-1]*jz[n-i+1]<<endl;
		ans=(ans+(sum[n]-sum[i-1]*jz[n-i+1]%mod+mod)%mod*fac[y]%mod*ifac[x]%mod*ifac[y-x]%mod)%mod;
	}
//	ans=(ans+sum[n]*(m==0)+mod)%mod;
	cout<<ans;
	return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值