2023牛客暑期多校训练营8(A/H/I/J)

目录

A.Alive Fossils

 H.Insert 1, Insert 2, Insert 3, ...

I.Make It Square

J.Permutation and Primes


A.Alive Fossils

思路:一开始题意看半天没看懂,后面发现只需要输出t组输入中,都出现过的字符串即可。

代码:

void solve() {
	int t;
	cin>>t;
	for(int i=1;i<=t;i++){
		int n;
		cin>>n;
		for(int i=1;i<=n;i++){
			string s;
			cin>>s;
			mp[s]++;
		}
	}
	vector<string>ans;
	for(auto x:mp){
		if(x.second==t){
			ans.push_back(x.first);
		}
	}
	cout<<ans.size()<<endl;
	for(auto x:ans) cout<<x<<endl;
}

 H.Insert 1, Insert 2, Insert 3, ...

思路:根据题意我们可以发现,因为数字是按顺序1,2,3......插,所以若一段区间[l,r]满足条件,那么[l,r'](r'<=r)也必然满足条件。我们可以从后往前遍历,也就是遍历区间[i,n](i从n遍历到1)。每次遍历到a[i]时,因为插入是顺序的,所以这个a[i],一定能和a[i]+1这个值对应,把它“抵消”掉,所以若一个区间合法,那么这个区间会被“抵消”完,第i个点的贡献为最左边的没被抵消完的端点-i。

举个例子吧,拿样例一来说,它的序列为:

1 1 2 2 3 3

从后往前遍历,遍历了i=5以及i=6,此时存入了两个3,它们都不合法:

不合法的值:3 3

不合法的下标:5 6

贡献为:最左边的没被抵消完的端点-i=5-5=0

然后遍历到i=4,此时a[i]=2,他能与一个a[i]+1抵消,也就是把3给抵消了,此时:

不合法的值:2 3

不合法的下标:4 6

贡献为:最左边的没被抵消完的端点-i=4-4=0

然后遍历到i=3,此时a[i]还是2,他还是能把3抵消,此时:

不合法的值:2 2

不合法的下标:3 4

贡献为:最左边的没被抵消完的端点-i=3-3=0

然后遍历到i=2,此时a[i]是1,他能把一个2抵消,此时:

不合法的值:2 (1不放入不合法的值中,因为单个1合法)

不合法的下标:4

贡献为:最左边的没被抵消完的端点-i=4-2=2

然后遍历到i=1,此时a[i]还是1,他能把一个2抵消,此时:

不合法的值: 无(1不放入不合法的值中,因为单个1合法)

不合法的下标:无

此时后面都合法了,贡献为i~n整个区间也就是6

总贡献为 :0+0+0+2+6=8

代码:

vector<int>ans,pos[maxn];
//ans存最入不满足条件的点
//pos[x]存入a[i]=x的位置
int a[maxn],st[maxn];
void solve() {
	int n,res=0;
	cin>>n;
	ans.push_back(n+1);//放入初始值,使得:若点i后面没有不满足条件的点,则答案加n-i+1,也就是i~n区间的贡献。
	for(int i=1; i<=n; i++)cin>>a[i];
	for(int i=n; i>=1; i--) {
		if(pos[a[i]+1].size()) {
			st[pos[a[i]+1].back()]=true;//这个点被上一个点"抵消"了,标记这个点已经合法了
			pos[a[i]+1].pop_back();//消掉
		}
		if(a[i]>1) {
			pos[a[i]].push_back(i);//存入值对应的下标
			ans.push_back(i);//存入不合法的位置
		}
		while(st[ans.back()])ans.pop_back();//把合法的都弹出
		res+=ans.back()-i;
	}
	cout<<res<<endl;
}

I.Make It Square

思路:若字符串s的长度小于t,我们将两个字符串翻转后交换,仍可以使得字符串s大于t,所以我们考虑s长度大于t的情况。

通过手玩样例我们可以发现,随着字符串p和q长度的增加,对于字符串s,分割点是固定的,分割点在后缀大小为(s.size()-t.size())/2。举个例子,比如样例三:

s=abbabbababbab,t=bab。

当p.size()=q.size()=1时:

 p+s+q+t=?abbabbababbab?bab

分割为 ?abbabbab  abbab?bab

当p.size()=q.size()=2时:

 p+s+q+t=??abbabbababbab??bab

分割为 ??abbabbab  abbab??bab

以??abbabbab  abbab??bab举例,

首先,若bab与abbabbab的后缀不重合,那么这两个部分的字符串怎么都不会相等。然后可以看出,我们可以通过字符串abbabbab作为后缀,字符串abbab作为前缀,来求出已经不固定的字符个数。

若这两个部分有重合,则判断重合部分是否一样,也就是abbabbab的重合前缀和abbab重合后缀是否一样,利用z函数判断即可,若重合部分一样则答案为1,反之答案为0。

若这两个部分没重合,则计算不固定字符的个数,答案为26的幂次。

具体实现见代码:

代码:

vector<int> zFunction(string s) {
	int n = s.size();
	vector<int> z(n + 1);
	z[0] = n;
	for (int i = 1, j = 1; i < n; i++) {
		z[i] = max(0ll, min(j + z[j] - i, z[i - j]));
		while (i + z[i] < n && s[z[i]] == s[i + z[i]]) {
			z[i]++;
		}
		if (i + z[i] > j + z[j]) {
			j = i;
		}
	}
	return z;
}
void solve() {
	k26[0]=1;
	for(int i=1; i<maxn; i++) {
		k26[i]=k26[i-1]*26%mod;
	}//预处理26的幂次
	string s,t;
	int n;
	cin>>n>>s>>t;
	int lens=s.size(),lent=t.size();
	if((lens+lent)%2) {//若字符串长度之和为奇数,那么它们怎么都不能分割成两个相等字符串
		for(int i=1; i<=n; i++)cout<<0<<" ";
		return;
	}
	if(lens<lent) {
		reverse(s.begin(),s.end());
		reverse(t.begin(),t.end());
		swap(s,t);
		swap(lens,lent);
	}
	if(s.substr((lens+lent)/2-lent,lent)!=t) {//判断字符串t作为后缀是否合法
		for(int i=1; i<=n; i++)cout<<0<<" ";
		return;
	}
	auto v=zFunction(s);
	reverse(v.begin(),v.end());
	//v[i]表示长度为i的后缀与前缀的最大公共前缀
	FOR(1,n) {
		int now=(lens+lent+2*i)/2;//当前一半的总字符串长度
		int p=abs(lens-now);
		if(lens>now) {//若有重合部分
			if(v[p]!=p) cout<<0<<" ";//重合部分是否全部相同
			else cout<<1<<" ";//答案为加1
		} else {
			cout<<k26[p]<<" ";//答案为26^(不确定字符的个数)
		}
	}
}

J.Permutation and Primes

思路:我们可以先构造长度为7的循环节:

i+3,i+6,i+1,i+4,i+7,i+2,i+5

那么若n%7不为0的时候怎么办呢?我们可以先把前面的长度为7循环节给赋值,然后最后7+n%7个数,我们随便找一组解满足它与循环节的末尾形成奇质数,放预处理后面即可。

代码:

int cmp[7][15] {
	{3,6,1,4,7,2,5},//长度为7的循环节
	{5,2,7,4,1,8,3,6},//最后8个数,此时n%7=1
	{9,6,3,8,5,2,7,4,1},//最后9个数,此时n%7=2
	{9,4,7,2,5,10,3,8,1,6},//最后10个数,此时n%7=3
	{11,8,5,10,7,2,9,4,1,6,3},//最后11个数,此时n%7=4
	{1,6,3,10,7,12,5,2,9,4,11,8},//最后12个数,此时n%7=5
	{1,4,9,2,7,12,5,10,3,6,13,8,11}//最后13个数,此时n%7=6
};
void solve() {
	cin>>n;
	int k=n/7;
	if(n==2) {
		cout<<"1 2"<<endl;
	} else if(n==3) {
		cout<<"1 2 3"<<endl;
	} else if(n==4) {
		cout<<"1 2 3 4"<<endl;
	} else if(n==5) {
		cout<<"1 4 3 2 5"<<endl;
	} else if(n==6) {
		cout<<"4 3 6 5 2 1"<<endl;
	}
	if(n<=6)return;
	for(int i=0; i<k-1; i++) {
		for(int j=0; j<7; j++) {
			a[i*7+j+1]=i*7+cmp[0][j];
		}
	}//前面的循环节赋值 
	for(int i=0; i<7+n%7; i++) {
		a[(k-1)*7+i+1]=(k-1)*7+cmp[n%7][i];
	}//最后放合法解 
	for(int i=1; i<=n; i++)cout<<a[i]<<" \n"[i==n];
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值