【08.28】Codeforces Round #741 (Div. 2) D2题 Two Hundred Twenty One (hard version) (思维+set+二分)

传送门

题意

有一个长度为n序列a,每个数要么为1,要么为-1。要求该序列的符号和为0,即\(\sum_{i = 1}^n (-1)^{i-1}a_i=0\)。有q次询问,每次询问长度为[l,r]的子序列最少要删除多少个数,才能保证该序列的符号和为0,并输出方案。

(\(1\leq n,q\leq 3\times 10^5\))

题解

先证明一个结论:对于一个奇数长度的序列删除一个数一定能使符号和为0。
为方便处理,对于原序列我们直接将偶数位置上的数取反,对于取反后的某一子序列来说,新序列的和要么等于原序列的符号和,要么是其相反数。相反数是没有影响的,因为最终只要把序列和变为0即可。
定义一个新序列b,\(b_i\)表示删除\(a_i\)后序列的符号和。假设删除前序列在\(a_i\)位置前的所有数和为x,在\(a_i\)位置后的所有数和为y,则新序列的符号和为\(x-y\)
试着比较\(b_i\)\(b_{i+1}\)

  1. \(a_i = -a_{i+1}\),则\(b_i=b_{i+1}\)
  2. \(a_i = a_{i+1}\),则\(|b_i-b_{i+1}|= 2\)

再来考虑\(b_1\)\(b_n\),可以发现对于奇数长度序列,不论\(a_1\)\(a_n\)如何取值,\(b_1*b_n<0\),也就是说一定存在一个点\(b_i=0\),故一定可以删一个数使得新序列符号和为0。

对于偶数长度序列来说,如果当前序列符号和已经为0,则不需要删数,否则先把最后一个位置的数删除变为奇数长度的数,再删一次即可,最多两次。

剩下的问题就只要想,如何在奇数长度的序列中找到那个应该删除的数,我的方法继承了证明的思路。假设给定序列的和为x,删除的数要么是1,要么是-1。那么若删除的数为1,则在删除数之前的所有前缀和为\(\frac{x-1}{2}\),也就是找一个当前数是1且前缀和为\(\frac{x-1}{2}\)的位置即可,删除的数若是-1也是同样的思路,建一个set找数即可。

时间复杂度为\(O(n\log n + q\log n)\)

官方题解给了一个更好写,且复杂度更小的做法,我就不写代码验证直接口胡一下做法:

  1. 先求出整个序列上每个位置的\(b_i\),对应询问给定的区间[l,r],相当于这一段上所有\(b_i\)减去\(sum_{l-1}\),加上\(suf_{r+1}\)
  2. 之后二分位置,假设二分的位置为x,若\(b_x=0\)则说明找到答案,否则,若\(b_x\)\(b_l\)异号则说明[l,x-1]上一定存在一个\(b_i=0\),否则去[x+1,r]上找。

这样的时间复杂度为\(O(n +q\log n)\)

代码

/*************************************************************************
	> File Name: 1.cpp
	> Author: Knowledge_llz
	> Mail: 925538513@qq.com 
	> Blog: https://www.cnblogs.com/Knowledge-Pig/ 
	> Created Time: 2021/8/26 23:46:43
 ************************************************************************/

#include<bits/stdc++.h>
#define For(i,a,b) for(int i=(a);i<=(b);++i)
#define LL long long
#define pb push_back
#define fi first
#define se second
#define pr pair<int,int>
#define mk(a,b) make_pair(a,b)
#define endl '\n'
using namespace std;
const int maxx = 3e5 + 10, delta = 3e5;
int n, q, dp[maxx], sum[maxx];
char str[maxx];
set<int> s[maxx << 1][2];
void Find(int l, int r){
	if(sum[r] - sum[l] == 0){
		cout << l << endl;
		return;
	}
	int x = sum[r] - sum[l - 1];
	int y = (x - 1) / 2 + sum[l - 1];
	if(!s[y + delta][1].empty()){
		auto it = s[y + delta][1].lower_bound(l);
		if(it != s[y + delta][1].end() && (*it) < r){
			cout << (*it) + 1 << endl;
			return;
		}
	}
	y = (x + 1) / 2 + sum[l - 1];
	if(!s[y + delta][0].empty()){
		auto it = s[y + delta][0].lower_bound(l);
		if(it != s[y + delta][0].end() && (*it) < r){
			cout << (*it) + 1 << endl;
			return;
		}
	}
}

	
int main(){
	ios::sync_with_stdio(false); cin.tie(0);
#ifndef ONLINE_JUDGE
	freopen("input.in", "r", stdin);
	freopen("output.out", "w", stdout);
#endif
	int T;
	cin >> T;
	while(T--){
		cin >> n >> q >> str + 1;
		if(str[1] == '+') dp[1] = 1;
		else dp[1] = -1;
		for(int i = 2; i <= n; ++i){
			dp[i] = dp[i - 2] + (str[i] == '+' ? 1 : -1);
			if(!(i & 1)) str[i] = (str[i] == '+') ? '-' : '+';
		}
		//cout << str + 1 << endl;
		for(int i = 1; i <= n; ++i){
			sum[i] = sum[i - 1] + (str[i] == '+' ? 1 : -1);
			if(i < n) s[sum[i] + delta][str[i + 1] == '+'].insert(i);
		//	cout << sum[i];
		}
		//cout << endl;

		while(q--){
			int l, r;
			cin >> l >> r;
			if((r - l + 1) & 1){
				cout << 1 << endl;
				Find(l, r);
			}
			else{
				int x = dp[r] - dp[l - 1];
				int y = dp[r - 1];
				if(l > 1) y -= dp[l - 2];
				if(x == y) cout << 0 << endl;
				else{
					cout << 2 << endl;
					cout << r << " ";
					Find(l, r - 1);
				}
			}
		}
		for(int i = 1; i <= n; ++i){
			s[sum[i] + delta][1].clear();
			s[sum[i] + delta][0].clear();
		}
	}
	return 0;
}
posted @ 2021-08-29 15:42  Knowledge-Pig  阅读(9)  评论(0)    收藏  举报