【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}\):
- 若\(a_i = -a_{i+1}\),则\(b_i=b_{i+1}\)。
- 若\(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)\)。
官方题解给了一个更好写,且复杂度更小的做法,我就不写代码验证直接口胡一下做法:
- 先求出整个序列上每个位置的\(b_i\),对应询问给定的区间[l,r],相当于这一段上所有\(b_i\)减去\(sum_{l-1}\),加上\(suf_{r+1}\)。
- 之后二分位置,假设二分的位置为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;
}

浙公网安备 33010602011771号