2023暑期牛客训练赛Day1 补题
反思
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;
}