2023NOIP A层联测18 划分

文章讨论了一个关于长度为n的01字符串S如何划分为k段,使得每段二进制数相加后最大值及划分方案数量的问题,利用组合数、字典序和哈希技巧给出了解决方法。

题目大意

对于一个长度为nnn010101字符串SSS,请求出将其分为至少kkk段,将每段看成二进制数求和后的最大值以及取到这个最大值的划分方案的数量。

输出最大值模998244353998244353998244353后的值和划分方案的数量模998244353998244353998244353后的值。

1≤n,k≤2×1061\leq n,k\leq 2\times 10^61n,k2×106


题解

如果前kkk位没有111,则最优解一定是第一个111之前所有间隔中选k−1k-1k1个及以上的间隔(因为把最后一段形成的二进制数从中间分开一定会减小),可以用组合数来计算。如果一个111都没有,则最优解就是n−1n-1n1个间隔中选k−1k-1k1个及以上的间隔。

如果不满足上面的条件,则最优解一定是选出一段长度为n−k+1n-k+1nk+1子串,剩下的每一个数单独分一段。我们考虑比较两种划分方案的大小。

设第一种划分方案的n−k+1n-k+1nk+1的串看成的二进制数为v1v_1v1,其余k−1k-1k1段中111的个数为t1t_1t1,第二种划分方案的n−k+1n-k+1nk+1的串看成的二进制数为v2v_2v2,其余k−1k-1k1段中111的个数为t2t_2t2。当v1>v2v_1>v_2v1>v2

  • 如果v1v_1v1v2v_2v2的前n−kn-knk位相同,而v1v_1v1的最后一位为111v2v_2v2的最后一位为000,则t1+1=t2t_1+1=t_2t1+1=t2,两种划分方案的结果是相同的
  • 如果v1v_1v1v2v_2v2的前n−kn-knk位存在不同,则设第一个不同的位为ttt
    • 如果t1≥t2t_1\geq t_2t1t2,则显然第一种方案更优
    • 如果t1<t2t_1<t_2t1<t2,则我们将SSS中的每个111减去对答案的贡献111,两种划分方案的大小关系不变。此时其余k−1k-1k1段中的111都不算贡献,最大段中每一个为111的位iii的贡献为2i−12^i-12i1,那么两种方案中前t−1t-1t1个位置的贡献相同,第一种方案中第ttt位的贡献为2t−12^t-12t1,而第二种划分方案中第ttt位为000,之后的位之和小于等于2t−12^t-12t1,因为每个111的贡献都被减去了111,所以之后的位的贡献之和小于2t−12^t-12t1,得第一种方案更优

由此可得,以1,2,…,k1,2,\dots,k1,2,,k开头,长度为n−k+1n-k+1nk+1的串中,划分出的串为字典序最大的串的结果最优。如果字典序最大的串的最后一位为111,前面n−kn-knk位与其相同,最后一位为000的串也是最优的。

那我们怎么求字典序最大的串呢?可以用二分哈希。从111kkk枚举iii,设lll为以111i−1i-1i1为起点的串中字典序最大的串的起点,那么对于当前的iii,我们要比较lll开头的串和iii开头的串的字典序的大小。那么,我们可以二分两个串最长的公共前缀,假设公共前缀为111ttt,则t+1t+1t+1即为两个串中第一个不同的位置,比较这个位置的大小即可知道两个串的大小关系。用哈希可以O(1)O(1)O(1)判断两个字符串是否相同,所以处理一个iii的时间复杂度是O(log⁡n)O(\log n)O(logn)的。

求出字典序最大的串之后,判断这个串的最后一位是否为111。如果是的话,将111改为000,再判断以1,2,…,k1,2,\dots,k1,2,,k开头,长度为n−k+1n-k+1nk+1的串中是否有与这个修改后的串相同的,这同样可以用哈希来O(1)O(1)O(1)比较。

最大值可以用字典序最大的串对应的二进制数加其余部分的111的个数来得到,方案数可以在比较大小和是否相同的时候得到,那么这道题就解决了。

注意要特判n==kn==kn==k的情况,此时最大值为SSS111的个数,方案数为111

注意代码中虽然使用了单哈希,但这样有一定可能将两个不同的串判断为相同的串(有可能,但可能性不大),所以最好使用双哈希。

时间复杂度为O(nlog⁡n)O(n\log n)O(nlogn)

code

#include<bits/stdc++.h>
using namespace std;
const int N=2000000;
const long long mod=998244353;
int n,k,len,sum[N+5];
long long ans1,ans2,p[N+5],pw[N+5],jc[N+5],ny[N+5];
char s[N+5];
long long mi(long long t,long long v){
	if(!v) return 1;
	long long re=mi(t,v/2);
	re=re*re%mod;
	if(v&1) re=re*t%mod;
	return re;
}
void init(){
	jc[0]=1;pw[0]=1;
	for(int i=1;i<=N;i++){
		jc[i]=jc[i-1]*i%mod;pw[i]=pw[i-1]*2%mod;
	}
	ny[N]=mi(jc[N],mod-2);
	for(int i=N-1;i>=0;i--) ny[i]=ny[i+1]*(i+1)%mod;
}
long long C(int x,int y){
	return jc[x]*ny[y]%mod*ny[x-y]%mod;
}
long long hsh(int l,int r){
	return (p[r]-p[l-1]*pw[r-l+1]%mod+mod)%mod;
}
int gt(int i,int j){
	int l=1,r=len,mid;
	while(l<=r){
		mid=l+r>>1;
		if(hsh(i,i+mid-1)==hsh(j,j+mid-1)) l=mid+1;
		else r=mid-1;
	}
	return l-1;
}
int main()
{
//	freopen("divide.in","r",stdin);
//	freopen("divide.out","w",stdout);
	init();
	scanf("%d%d",&n,&k);len=n-k+1;
	scanf("%s",s+1);
	for(int i=1;i<=n;i++){
		sum[i]=sum[i-1]+s[i]-'0';
		p[i]=(p[i-1]*2+s[i]-'0')%mod;
	}
	if(!sum[n]){
		for(int i=k;i<=n;i++){
			ans2=(ans2+C(n-1,i-1))%mod;
		}
		printf("0 %lld",ans2);
		return 0;
	}
	if(k==n){
		printf("%lld 1",sum[n]);
		return 0;
	}
	if(sum[n-len+1]==0){
		int fst=1;
		while(s[fst]!='1') ++fst;
		ans1=hsh(fst,n);
		for(int i=k;i<=fst;i++){
			ans2=(ans2+C(fst-1,i-1))%mod;
		}
		printf("%lld %lld",ans1,ans2);
		return 0;
	}
	int l=1;ans2=1;
	for(int i=2;i+len-1<=n;i++){
		int vt=gt(l,i);
		if(vt==len) ++ans2;
		else if(s[l+vt]<s[i+vt]){
			l=i;ans2=1;
		}
	}
	if(s[l+len-1]=='1'){
		int hs=(hsh(l,l+len-1)-1)%mod;
		for(int i=1;i+len-1<=n;i++){
			if(hs==hsh(i,i+len-1)) ++ans2;
		}
	}
	ans1=hsh(l,l+len-1)+sum[l-1]+sum[n]-sum[l+len-1];
	printf("%lld %lld",ans1,ans2);
	return 0;
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值