Problem: A题 Almost Correct

题意

  • 给一个长度为n的未排序 10101ss2
  • 给一个操作:选中0101串中两个位置不同的数进行交换,如果这两个数位于前面的数不大于后面则不交换(即这次交换作废)
  • 求如何操作可以使得除了题给字符串以外的所有长度为nn的字符串排序。(就是说一个怎样的操作序列可以使其他同长度的0101字符都被排序,但题给的不被排序)

思路

我们可以考虑给定字符串与其他字符串的不同之处,首先可以发现当前字符串与其他字符串肯定是存在某些地方0101不同,但这样的不同没有共性,我们想能不能把它给固定,使得给定字符串与其他字符串都有相同的不同之处。

首先我们考虑固定一个值,就是在一些操作后只有给定字符串的第一位为11,其他字符串第一位都是00,发现必然不能实现,因为想保持某一位为11,就只能让这一位对其前面数和后面的为11的位置操作,但必然有与给定字符串不同的字符串但是后面11的位置相同。

再考虑固定两个值,我们可以固定一个最左边的11(设该下标为ll),和最右边的00(设该下标为rr)。操作方式就是这个11与其他的11交换,00与其他的00进行交换。为什么可以因为对于和给定字符串11位置相同00位置相同只能是给定字符串本身,其他的字符串一定有不同的地方,这个不同的地方就会导致这两个位置的与给定字符串不同。至此,我们发现了一个使给定字符串特殊于所有其他字符串的地方。

我们找到了不同,然后可以对除了llrr以外所位置进行排序,排完序后因为ll位置为11rr位置为00(因为给定字符串本身保证未排序,所以l<rl < r),给定字符串一定是未成功排序的。同时,其他字符串这两个位置一定最少有一个是被成功排序的。我们取l0l0为给定字符串中0的个数(l<=l0<rl <= l0 < r),发现给定字符串的0101分界线一定再l0l0l0+1l0 + 1之间,而其他的未排序成功的字符串0101分界线一定向左向右有所偏移。因此,只要将ll[1,l0][1,l0]内交换,r和[l0+1,n][l0 + 1, n]交换即可。

解题方法

按照思路中来:

  1. 将最左边11和所有11来一遍交换,最右边00和所有00交换。
  2. 将除了lrl,r以外位置排序,因为操作的性质,导致所有11一定是向后移动,00向前。所以只要对每个位置和后面所有位置实施交换即可。
  3. ll[1,l0][1, l0]内交换,注意先和最远处交换,即先和l0l0,再l01...l0 - 1...,先11,再2...r2...,r[l0+1,n][l0 + 1, n]交换同理。

复杂度

  • 时间复杂度:

每次操作是 n2+(n2)(n3)/2+n2n - 2 + (n - 2) * (n - 3) / 2 + n - 2,最多为119119,大概是O(n2)O(n^2)

  • 空间复杂度:

开了个数组存每次操作,是O(n2)O(n^2)

Code

#include <bits/stdc++.h>

#pragma GCC optimize(2)
using namespace std;

#define endl "\n"
#define ll long long
#define pii pair<int, int>

void solve()
{
	int n;
	string s;
	cin >> n >> s;

  	//操作1
	vector<int> S0, S1;
	for(int i = 0; i < n; i++){
		if(s[i] == '0') S0.emplace_back(i + 1);
		else S1.emplace_back(i + 1);
	}

	int l = S1.front(), r = S0.back(), l0 = S0.size(), l1 = S1.size();
	vector<pii> res;
	for(int i = 1; i < l1; i++) res.emplace_back(make_pair(l, S1[i]));
	for(int i = 0; i < l0 - 1; i++) res.emplace_back(make_pair(S0[i], r));
  
  	//操作2
	for(int i = 1; i <= n; i++){
		if(i == l || i == r) continue;

		for(int j = i + 1; j <= n; j++){
			if(j == l || j == r) continue;
			
			res.emplace_back(make_pair(i, j));
		}
	}

  	//操作3
	for(int i = 1; i < l; i++) res.emplace_back(make_pair(i, l));
	for(int i = l0; i > l; i--) res.emplace_back(make_pair(l, i));
	for(int i = l0 + 1; i < r; i++) res.emplace_back(make_pair(i, r));
	for(int i = n; i > r; i--) res.emplace_back(make_pair(r, i));

  	//输出
	cout << res.size() << endl;
	for(auto [x, y] : res) cout << x << ' ' << y << endl;
}

int main()
{
	cout.tie(nullptr);
	cin.tie(nullptr);
	ios::sync_with_stdio(false);

	int _ = 1;
	cin >> _;

	while (_--)
		solve();

	return 0;
}

PS:如果有不足之处,欢迎大家批评指正


  1. 排好序的0101串是不存在任何两个位置使得1100前面,即000...01111...1000...01111...1
  2. 全是0011组成的字符串