CF1839C

发布时间 2023-10-13 21:30:01作者: 悲伤鳄鱼吃面包

Insert Zero and Invert Prefix

题面翻译

你有一个长度为 \(n\) 的 01 序列 \(a\),以及一个初始为空的序列 \(b\)。你接下来需要执行 \(n\) 次操作,每次操作将会使序列 \(b\) 的长度增加 \(1\)

  • 在第 \(i\) 次操作,你需要选择一个在 \([0,i)\) 之间的整数 \(p\)。然后在第 \(p\) 位之后插入一个数字 \(0\),并翻转之前的所有数字。
  • 即,设在第 \(i\) 次操作前的 \(b\)\(b_1,b_2,...,b_{i-1}\)。在第 \(i\) 次操作选择在 \([0,i)\) 之间的整数 \(p\),并使 \(b\) 变为 \(\overline{b_1}, \overline{b_2}, \ldots, \overline{b_{p}}, 0, b_{p+1}, b_{p+2}, \ldots, b_{i-1}\)。其中,\(\overline{x}\) 表示二进制翻转操作,即 \(\overline{0}=1,\overline{1}=0\)

对给定的数组 \(a\),判断是否存在一组操作使得 \(b\) 变为 \(a\),若存在则输出方案。

多组数据。

题目描述

You have a sequence $ a_1, a_2, \ldots, a_n $ of length $ n $ , each element of which is either $ 0 $ or $ 1 $ , and a sequence $ b $ , which is initially empty.

You are going to perform $ n $ operations. On each of them you will increase the length of $ b $ by $ 1 $ .

  • On the $ i $ -th operation you choose an integer $ p $ between $ 0 $ and $ i-1 $ . You insert $ 0 $ in the sequence $ b $ on position $ p+1 $ (after the first $ p $ elements), and then you invert the first $ p $ elements of $ b $ .
  • More formally: let's denote the sequence $ b $ before the $ i $ -th ( $ 1 \le i \le n $ ) operation as $ b_1, b_2, \ldots, b_{i-1} $ . On the $ i $ -th operation you choose an integer $ p $ between $ 0 $ and $ i-1 $ and replace $ b $ with $ \overline{b_1}, \overline{b_2}, \ldots, \overline{b_{p}}, 0, b_{p+1}, b_{p+2}, \ldots, b_{i-1} $ . Here, $ \overline{x} $ denotes the binary inversion. Hence, $ \overline{0} = 1 $ and $ \overline{1} = 0 $ .

You can find examples of operations in the Notes section.

Determine if there exists a sequence of operations that makes $ b $ equal to $ a $ . If such sequence of operations exists, find it.

输入格式

Each test contains multiple test cases. The first line contains a single integer $ t $ ( $ 1 \le t \le 10^4 $ ) — the number of test cases.

The first line of each test case contains one integer $ n $ ( $ 1 \le n \le 10^5 $ ) — the length of the sequence $ a $ .

The second line of each test case contains $ n $ integers $ a_1, a_2, \ldots, a_n $ ( $ 0 \le a_i \le 1 $ ) — the sequence $ a $ .

It is guaranteed that the sum of $ n $ over all test cases does not exceed $ 10^5 $ .

输出格式

For each test case:

  • output "NO", if it is impossible to make $ b $ equal to $ a $ using the given operations;
  • otherwise, output "YES" in the first line and $ n $ integers $ p_1, p_2, \ldots, p_n $ ( $ 0 \le p_i \le i-1 $ ) in the second line — the description of sequence of operations that makes $ b $ equal to $ a $ . Here, $ p_i $ should be the integer you choose on the $ i $ -th operation. If there are multiple solutions, you can output any of them.

样例 #1

样例输入 #1

4
5
1 1 0 0 0
1
1
3
0 1 1
6
1 0 0 1 1 0

样例输出 #1

YES
0 0 2 1 3
NO
NO
YES
0 1 0 2 4 2

分析

要我们输出每次插入的位置

我们只能插入0, 所以1一定是靠取反得来的.

把插入0的位置之前的所有数取反, 要得到1, 1的右边一定要有0, 否则不可能, 换句话说就是如果 \(a[n]=1\), 肯定无解, 其他情况总能找到答案

那我们就来考虑构造方式, 取反会影响插入位置的左边的所有数, 那我们是不是要优先得到右边的1呢, 因为如果先得到了左边的1, 在去得到右边的1的时候, 肯定要在右边的位置插入, 而插入取反肯定会对已经得到的左边1造成影响

我们可以从右往左遍历, 把一段连续的0和连续的1当作一段来处理, 例如 111000 这样的当作一段, 如果是 001100, 那么最左边 00 是一段, 1100 是另一段, 我们从右往左遍历得到一些段, 然后我们优先处理靠右的段, 我们只需要在处理好的一段的最左边插入0, 就可以不影响已经构造好的段了

对于每一段而言, 我们得到的段肯定是一串连续1和一串连续0, 我们看在这一段中最右边的1出现在哪里, 然后我们就在这个位置右边记为 p 插入0即可, 所以我们完全可以一开始全插入在最左边插入, 然后最后一个0就插入在 p 就可以

不难看出, 总共的时间复杂度是 \(O(n)\)

代码

#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
const int N = 1e5 + 10;
vector<int> v[N];	//放一段一段的1和0
int a[N];

int main()
{
	ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
	
	int t; cin >> t;
	while(t--)
	{
		int n; cin >> n;
		for(int i = 1; i <= n; i++)
			cin >> a[i];
		if(a[n])
		{
			cout << "No\n"; continue;
		}
		cout << "YES\n";
		int last = 0, idx = 0; //idx表示有几段
		for(int i = n; i > 0; i--)
		{
			if(last && !a[i])	//如果上一个是1但是这一个是0, 那说明进入了新的一段 
			{
				idx++; 
			}
			v[idx].push_back(a[i]);
			last = a[i];
		}
		vector<int> ans;
		for(int i = 0; i <= idx; i++)
		{
             //r是最右边的1的位置, 如果没有1那么说明全是0, 那插入就要在最左边插入, 刚好就是0
			int sz = v[i].size(), r = 0;	
            //因为是从右往左遍历塞入v, 所以还要翻转一下
			reverse(v[i].begin(), v[i].end());	
			for(int j = 0; j < sz - 1; j++)
			{
				if(v[i][j] && !v[i][j+1])
				{
					r = j + 1; break;	//找到这一段最右边的那个1的位置 
				}
			}
			//先放进去len-1个0, 插在开头位置 
			for(int j = 0; j < sz - 1; j++)
			{
				ans.push_back(0); 
			}
			ans.push_back(r);
		}
		for(auto &item : ans) cout << item << ' ';
		cout << endl;
		//多例清空 
		for (int i = 0; i <= idx; i++)
			v[i].clear();
	}
}