2023暑期牛客训练赛Day1 补题

发布时间 2023-07-18 00:41:33作者: Wannabtl

反思

J题想的很快,但写了很久也没写出来,问题出在式子的细节写了半天出了问题。下次把式子考虑好,再进行代码实现。K题没开longlong产生了罚时。L题想出来了,但最后剩下的时间不够也没有来得及写。A题离正解很近,就是处理冗余操作,最后一直在想怎么把0,1相关位置的操作隔离出来。实际上只用隔离两个选定的特殊值即可(比如最前的1和最后的0)

补题

A

给定一个01序列\(S\),构造一组操作,每次操作\((x,y)\)\(S_x,S_y\) 排序,使得经过该组操作,除了给与的序列,都能被正确排序(0在前1在后)

考试的时候想到了要使最前的1和最后的0作为关键点对,但构造的方法步骤数超过了限制。问题出在使用了很多重复的操作步骤。

在正解中,通过将\(l,r\),即最前1的位置,和最末0的位置提取出来,第一步对剩下的值排序,第二步对l,r进行单独操作,即l,r和另一个操作数,从而保证了不出现重复的操作

而操作的构造如下:

  • 首先,记录序列S中,所有0的位置\(S0\)和所有1的位置\(S1\),构造操作将0位置最大值移到最后面(即r),1位置最小值移到最前面(即l)。这样的效果是使得除了给定序列,要么l会变成0,要么r会变成1。考虑S0的位置中有1会被置后,S1的位置有0则会被置前
  • 将除l,r之外的值排序,此时在除开l,r之外,会形成0...01..1(其中|S0|-1个零和|S1|-1个1)。
  • 将l,r上的0,1刚好置于相邻位置,形成0...001011...1。因为题目保证给予的序列是非排序的,所以此时\(l<|S0|\)\(r>|S0|\) 。通过交换将l上的1放到|S0|的位置,r上的0放到|S0|+1的位置即可。而l,r任意一个数改变,都会使得该序列有序化
  • 注意,为了防止重复操作,每个操作都是由l,r和另一个数构成。分四段操作1l-1和l,l+1|S0|和l;r+1N和r,|S0|+1r-1和r。注意操作顺序是从远往近,否则对于正常操作会出现10011变成01011的结果(l上的1应该和最远的0换,从而和后续的1连接)
#include<bits/stdc++.h>
using namespace std;
const int nn=20;
int main(){
	int _; 
	for(cin>>_;_--;){
		int n;
		cin>>n;
		string s;
		cin>>s;
		vector<int>s0,s1;
		int l=0,r=0;
		for(int i=0;i<s.length();i++){ //统计0、1的位置
			if(s[i]=='0'){
				r=i+1;
				s0.push_back(i+1);
			}
			else {
				if(!l) l=i+1;
				s1.push_back(i+1);
			}
		}
		vector<pair<int,int> >ans; //记录答案
		for(auto i:s0) //将最大值放到r
		if(i!=r){
			ans.push_back(make_pair(i,r));
		}
		for(auto i:s1) //将最小值放到l
		if(i!=l){
			ans.push_back(make_pair(l,i));
		}

		//将出l,r的排序
		for(int i=1;i<=n;i++) if(i!=l&&i!=r)
		for(int j=i+1;j<=n;j++) if(j!=l&&j!=r){
			ans.push_back(make_pair(i,j));
		}

		//排l,从远到近
		for(int i=s0.size();i>l;i--){
			ans.push_back(make_pair(l,i));
		}
		for(int i=1;i<l;i++){
			ans.push_back(make_pair(i,l));
		}
		//排r
		for(int i=s0.size()+1;i<r;i++){
			ans.push_back(make_pair(i,r));
		}
		for(int i=n;i>r;i--){
			ans.push_back(make_pair(r,i));
		}
		cout<<ans.size()<<endl;
		for(int i=0;i<ans.size();i++)
		 cout<<ans[i].first<<" "<<ans[i].second<<endl;
	}
	return 0;
}