【2023NOI模拟赛13】sequence 做题记录

有⼀个⻓度为 nn 的序列 ss,每⼀位为 *+,你需要选择⼀个 ss 的⼦序列 tt,使得 f(t)mod2kf(t) \mod 2^k 最⼤。

f(t)f(t) 表示依次遍历 tt 中元素后的 xx 值:ii11 开始,如果 tit_i*xx22 ,如果 tit_i+xx11xx 初始为 00

注意:⼀个序列 ttss 的⼦序列,当且仅当 tt 可以通过删除 ss 中⼀些位置的元素得到。

1n,k1061\le n,k\le 10^6

先维护出一个数组 aa,求出每个 + 后面 * 的个数 xx,即这个 + 最多可以贡献到 2x2^x,然后让 axax+1a_x\to a_x+1

接下来贪心的进位,按 ii 从小到大遍历 aa,若 ai3a_i\ge 3 则让 ai+1ai+1+1a_{i+1}\to a_{i+1}+1aiai2a_i\to a_i-2,注意到这样可以让来自相邻两个 *+aa 上留下连续的一段 11 或者 22,并且可以避免进了不需要进的位的情况。

然后就可以求出答案了,从高位往低位贪心,设当前是第 ii 位,那么:

  • 存在 jij\ge i 满足 aj=0a_j\not=0:那么直接让这一位的 11 贡献给第 ii 位,ansi=1ans_i=1,并且让 aj=0a_j=0。注意到由于来自相邻两个 *+aa 上留下连续的一段 11 或者 22,这些 + 会在答案中留下连续的一段 11,所以这样”强扯“过来是可行的,相当于把最后一个 * 从子序列中删去;
  • 不存在 jij\ge i 满足 aj=0a_j\not=0:那么要么 ansi=0ans_i=0,要么找到 11111121111112 这样的一段 aa 进位到 aia_i,由于进位之后这些位置都会变成 00,所以直接暴力找即可;

由于高位为 11 一定更优,所以这样贪心是对的,时间复杂度 O(n)O(n)

代码如下:

// Problem: #A. sequence
// Contest: Hydro
// URL: http://oiclass.com/d/AKNOI/p/185?tid=63fd6c6355e89e02d91afefd
// Memory Limit: 512 MB
// Time Limit: 1000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

#include <iostream>
#include <cstdio>

using namespace std;

const int S=1000005;

int n,k;
char str[S];
int sum[S],a[S*2];
int ans[S];

int main()
{
	freopen("sequence.in","r",stdin);
	freopen("sequence.out","w",stdout);
	scanf("%d%d",&n,&k);
	k--;
	scanf("%s",str+1);
	sum[n]=str[n]=='*';
	for(int i=n-1;i>=1;i--) sum[i]=sum[i+1]+(str[i]=='*');
	for(int i=1;i<=n;i++) if(str[i]=='+') a[sum[i]]++;
	for(int i=0;i<=n*2;i++)
	{
		if(a[i]>=3)
		{
			a[i+1]+=a[i]/2-(a[i]&1^1);
			a[i]=1+(a[i]&1^1);
		}
	}
	for(int i=k,j=n*2;i>=0;i--)
	{
		while(j>=i&&a[j]==0) j--;
		if(j>=i) a[j]=0,ans[i]=1;
		else
		{
			int res=-1;
			for(int l=i-1;l>=0;l--)
			{
				if(a[l]==0) break;
				if(a[l]==2)
				{
					res=l;
					break;
				}
			}
			if(res!=-1)
			{
				for(int l=res;l<=i-1;l++) a[l]=0;
				ans[i]=1;
			}
		}
	}
	bool flg=false;
	for(int i=k;i>=0;i--)
	{
		if(ans[i]==1) flg=true;
		if(flg) printf("%d",ans[i]);
	}
	if(!flg) puts("0");
	else printf("\n");
	return 0;
}