2023NOIP A层联测18 划分

题目大意

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

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

1 ≤ n , k ≤ 2 × 1 0 6 1\leq n,k\leq 2\times 10^6 1n,k2×106


题解

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

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

设第一种划分方案的 n − k + 1 n-k+1 nk+1的串看成的二进制数为 v 1 v_1 v1,其余 k − 1 k-1 k1段中 1 1 1的个数为 t 1 t_1 t1,第二种划分方案的 n − k + 1 n-k+1 nk+1的串看成的二进制数为 v 2 v_2 v2,其余 k − 1 k-1 k1段中 1 1 1的个数为 t 2 t_2 t2。当 v 1 > v 2 v_1>v_2 v1>v2

  • 如果 v 1 v_1 v1 v 2 v_2 v2的前 n − k n-k nk位相同,而 v 1 v_1 v1的最后一位为 1 1 1 v 2 v_2 v2的最后一位为 0 0 0,则 t 1 + 1 = t 2 t_1+1=t_2 t1+1=t2,两种划分方案的结果是相同的
  • 如果 v 1 v_1 v1 v 2 v_2 v2的前 n − k n-k nk位存在不同,则设第一个不同的位为 t t t
    • 如果 t 1 ≥ t 2 t_1\geq t_2 t1t2,则显然第一种方案更优
    • 如果 t 1 < t 2 t_1t1<t2,则我们将 S S S中的每个 1 1 1减去对答案的贡献 1 1 1,两种划分方案的大小关系不变。此时其余 k − 1 k-1 k1段中的 1 1 1都不算贡献,最大段中每一个为 1 1 1的位 i i i的贡献为 2 i − 1 2^i-1 2i1,那么两种方案中前 t − 1 t-1 t1个位置的贡献相同,第一种方案中第 t t t位的贡献为 2 t − 1 2^t-1 2t1,而第二种划分方案中第 t t t位为 0 0 0,之后的位之和小于等于 2 t − 1 2^t-1 2t1,因为每个 1 1 1的贡献都被减去了 1 1 1,所以之后的位的贡献之和小于 2 t − 1 2^t-1 2t1,得第一种方案更优

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

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

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

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

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

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

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

code

#include
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;
}

你可能感兴趣的:(题解,好题,题解,c++)