[省选联考 2026] 摩卡串 / string

小摩卡是天才。


给定长度为 \(n\) 的 01 串 \(s\) 和整数 \(k\)。长度为 \(m\) 的 01 串 \(t\) 合法,当且仅当:

  • \(s\) 是 \(t\) 的子串

  • \(t\) 中恰好有 \(k\) 个子串字典序小于 \(s\)

给出任意一个最短的合法 01 串 \(t\),或报告无解。

\(t \leq 5\) 组数据,\(1 \leq n \leq 200\),\(1 \leq k \leq 3000\)。


字典序小于 \(s\) 的字符串有以下两种构成方式:

  • \(s\) 的非自身的前缀,如 \(11001\) 存在符合条件的字符串 \(1,11,110,1100\)

  • 第一个不同字符小于 \(s\) 的,如 \(11001\) 存在字符串 \(0\dots,10\dots,11000\dots\)

需要注意的是,上面第二种情况的省略号部分可以为空。

我们考虑 \(t\) 一开始为空,往 \(t\) 的末尾不断追加字符。

对于第一种字符串,考虑构建 kmp 自动机,维护当前在自动机上的节点。

我们容易处理出每个节点能够匹配的前缀个数,累加即可。

同时这样我们可以处理第一条限制,只要过程中跳到了 kmp 自动机的 \(n\) 号节点即可。

对于第二种字符串,容易发现每次加入一个字符会产生两种贡献:

  • 新增加的第二种字符串,需要加入 \(0\) 并且能够匹配

  • 对于每个第二种字符串,贡献都要加 \(1\)

容易发现,新增的第二种字符串个数和上面的个数一样,都是可以预处理的。

我们需要记录的信息有:

  • 目前是否跳到过 kmp 自动机的 \(n\) 号节点

  • 当前在 kmp 自动机上的节点编号

  • 当前产生的第二种字符串个数

  • 当前字典序小于 \(s\) 的子串个数

我们需要找的是从 \((0,0,0,0)\) 到 \((1,?,?,k)\) 的最短路,bfs 解决,构造方案简单。

这样就可以做到 \(O(\sum n k^2)\) 了。

精细分析复杂度可以到 \(O(\sum n k^{1.5})\)。

//Ad astra per aspera
#include<iostream>
#include<cstdio>
#include<string>
#include<queue>
#include<algorithm>
using namespace std;
const bool can_0=true,can_1=true;
const int N=200,M=80,K=3000;
char ch[N+10];
int border[N+10],trans[N+10][2],cnt_1[N+10],cnt_2[N+10];
struct Node{
	char num;
	short num_1,num_2,num_3,num_4;
}O;
Node lst[2][N+10][M+10][K+10];
bool vis[2][N+10][M+10][K+10]; 
void solve(){
	int n,k;
	scanf("%d %d",&n,&k);
	for(int i=0;i<=n;i++){
		border[i]=0;
		trans[i][0]=trans[i][1]=0;
		cnt_1[i]=cnt_2[i]=0;
	}
	for(int i=1;i<=n;i++){
		cin>>ch[i];
	}
	for(int i=2;i<=n;i++){
		border[i]=border[i-1];
		while(border[i]  &&  ch[border[i]+1]!=ch[i]){
			border[i]=border[border[i]];
		}
		if(ch[border[i]+1]==ch[i]){
			border[i]++;
		}
	}
	trans[0][ch[1]-'0']=1;
	for(int i=1;i<n;i++){
		int cmd=ch[i+1]-'0';
		trans[i][cmd]=i+1;
		trans[i][cmd^1]=trans[border[i]][cmd^1];
	}
	trans[n][0]=trans[border[n]][0];
	trans[n][1]=trans[border[n]][1];
	for(int i=0;i<n;i++){
		if(ch[i+1]=='1'){
			cnt_2[i]=1;
		}
	}
	for(int i=1;i<n;i++){
		cnt_1[i]=1;
	}
	for(int i=1;i<=n;i++){
		cnt_1[i]+=cnt_1[border[i]];
		cnt_2[i]+=cnt_2[border[i]];
	}
	for(int s1=0;s1<2;s1++){
		for(int s2=0;s2<=n;s2++){
			for(int s3=0;s3<=M;s3++){
				for(int s4=0;s4<=k;s4++){
					lst[s1][s2][s3][s4]=O;
					vis[s1][s2][s3][s4]=false;
				}
			}
		}
	}
	queue<short> q1,q2,q3,q4;
	q1.push(0);
	q2.push(0);
	q3.push(0);
	q4.push(0);
	vis[0][0][0][0]=true;
	short t1=-1,t2=-1,t3=-1,t4=-1;
	while(!q1.empty()){
		short u1=q1.front();
		q1.pop();
		short u2=q2.front();
		q2.pop();
		short u3=q3.front();
		q3.pop();
		short u4=q4.front();
		q4.pop();
		if(can_0){
			int v1=u1,v2=u2,v3=u3,v4=u4;
			v3+=cnt_2[v2];
			v2=trans[v2][0];
			if(v2==n) v1=true;
			v4+=cnt_1[v2];
			v4+=v3;
			if(v3<=M  &&  v4<=k){
				if(!vis[v1][v2][v3][v4]){
					vis[v1][v2][v3][v4]=true;
					lst[v1][v2][v3][v4]=(Node){'0',u1,u2,u3,u4};
					q1.push(v1);
					q2.push(v2);
					q3.push(v3);
					q4.push(v4);
					if(v1==1  &&  v4==k){
						t1=v1;
						t2=v2;
						t3=v3;
						t4=v4;
						break;
					}
				}
			}
		}
		if(can_1){
			int v1=u1,v2=u2,v3=u3,v4=u4;
			v2=trans[v2][1];
			if(v2==n) v1=true;
			v4+=cnt_1[v2];
			v4+=v3;
			if(v3<=M  &&  v4<=k){
				if(!vis[v1][v2][v3][v4]){
					vis[v1][v2][v3][v4]=true;
					lst[v1][v2][v3][v4]=(Node){'1',u1,u2,u3,u4};
					q1.push(v1);
					q2.push(v2);
					q3.push(v3);
					q4.push(v4);
					if(v1==1  &&  v4==k){
						t1=v1;
						t2=v2;
						t3=v3;
						t4=v4;
						break;
					}
				}
			}
		}
	}
	string ans;
	if(t1==-1  &&  t2==-1  &&  t3==-1  &&  t4==-1){
		ans="Impossible";
	}
	else{
		while(lst[t1][t2][t3][t4].num){
			ans+=lst[t1][t2][t3][t4].num;
			Node tmp=lst[t1][t2][t3][t4];
			t1=tmp.num_1;
			t2=tmp.num_2;
			t3=tmp.num_3;
			t4=tmp.num_4;
		}
		reverse(ans.begin(),ans.end());
	}
	cout<<ans<<endl;
}
int main(){
	int test_id,test_cnt;
	scanf("%d %d",&test_id,&test_cnt);
	while(test_cnt--){
		solve();
	}
	return 0;
}

这是简单题啊,那我为啥没过。

posted @ 2026-03-17 17:12  Oken喵~  阅读(79)  评论(0)    收藏  举报