2018.10.31【校内模拟】几串字符(数位DP)(球盒问题)

本文探讨了一种使用数位DP和组合数解决特定字符串计数问题的方法,重点在于处理字符串中0和1的分布特性,通过预处理组合数简化计算过程,适用于有长度和上界限制的问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

传送门


解析:

这个数位DP是 O ( l e n ) O(len) O(len)的。。。

只需要预处理组合数就行了。。。

思路:

首先注意到几个性质。
1. c 0 , 1 + 1 ≥ c 1 , 0 ≥ c 0 , 1 c_{0,1}+1≥c_{1,0}≥c_{0,1} c0,1+1c1,0c0,1这个很显然,因为 1 / 0 1/0 1/0区间的出现是交替的。
2. c 1 , 0 + c 0 , 0 c_{1,0}+c_{0,0} c1,0+c0,0是0出现的次数。考虑统计每个串中靠后的0。
3. c 0 , 1 + c 1 , 1 + 1 c_{0,1}+c_{1,1}+1 c0,1+c1,1+1 是1出现的次数。考虑统计每个串中靠后的1和开头的1。

c 1 , 0 c_{1,0} c1,0实际上是全 0 0 0区间的个数,同理 c 0 , 1 + 1 c_{0,1}+1 c0,1+1实际上是全1区间的个数。

如果我们的限制只有长度那就好了,直接上球盒问题就可以做了。

但是有上界的限制。。。

没关系,我们只需要处理前缀和它的相同,某一位开始比它小的情况,那么后面就可以乱填了。

其实就是处理在某一位为 1 1 1的地方填 0 0 0,后面就可以球盒问题乱搞了。

注意一下要判断我们强制填 0 0 0的这个地方是同前面的0同属于一个盒子还是需要重新分配一个盒子。


代码:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define re register
#define gc getchar
#define pc putchar
#define cs const

cs ll mod=1000000007;
cs int N=100005;
int fac[N],inv[N],ifac[N];
inline int C(int n,int m){
	return (ll)fac[n]*ifac[m]%mod*ifac[n-m]%mod;
}

inline int calc(int n,int m){
	if(m>n)return 0;
	if(m==0)return n==0;
	return C(n-1,m-1);
}

string l,r;
int c[2][2],tot=1;

inline int solve(cs string &a){
	if(a.size()-1<tot||(c[0][1]+1!=c[1][0]&&c[1][0]!=c[0][1]))return 0;
	if(a.size()-1>tot)return 1ll*calc(c[0][1]+c[1][1]+1,c[0][1]+1)*calc(c[1][0]+c[0][0],c[1][0])%mod;
	
	int ans=0,t1=0,t0=0,cnt1=0,cnt0=0;
	for(int re i=1;i<=tot;++i){
		if(a[i]^a[i-1])(a[i]=='1')?++t1:++t0;
		(a[i]=='1')?++cnt1:++cnt0;
		
		if(t1>c[0][1]+1||t0>c[1][0]||cnt1>c[1][1]+c[0][1]+1||cnt0>c[0][0]+c[1][0])break;
		if(a[i+1]=='1'){
			int res0=c[0][0]+c[1][0]-cnt0,res1=c[1][1]+c[0][1]+1-cnt1;
			int rest0=c[1][0]-t0-(a[i]!='0')+1,rest1=c[0][1]+1-t1;
			ans=(1ll*ans+1ll*calc(res1,rest1)*calc(res0,rest0)%mod)%mod;
		}
		else if(i==tot&&cnt0==c[0][0]+c[1][0]&&cnt1==c[1][1]+c[0][1]+1&&t0==c[1][0]&&t1==c[0][1]+1)++ans;
	}
	return ans%mod;
}

signed main(){
	fac[0]=fac[1]=ifac[0]=ifac[1]=inv[0]=inv[1]=1;
	for(int re i=2;i<=100000;++i){
		fac[i]=1ll*fac[i-1]*i%mod;
		inv[i]=(mod-mod/i)*inv[mod%i]%mod;
		ifac[i]=1ll*ifac[i-1]*inv[i]%mod;
	}
	cin>>l>>r;l=" "+l;r=" "+r;
	for(int re i=0;i<2;++i)
	for(int re j=0;j<2;++j)scanf("%d",&c[i][j]),tot+=c[i][j];
	int ans=(solve(r)-solve(l)+mod)%mod;
	for(int re i=2;i<l.size();++i)--c[l[i-1]^48][l[i]^48];
	if(!c[0][0]&&!c[1][0]&&!c[1][1]&&!c[0][1])++ans;
	cout<<ans%mod<<endl;
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值