Codeforces1562D2 Two Hundred Twenty One (hard version) (思维)

题目链接: Two Hundred Twenty One (hard version)

大致题意

给定长度为n, 且仅由’+’, '-'构成的序列, 分别表示这个位置的值为+1, -1.
现在从中取出 [ l , r ] [l, r] [l,r], 组成长度为 r − l + 1 r - l + 1 rl+1的新序列 a a a. 定义: C = a 1 − a 2 + a 3 − a 4 . . . C = a_1 - a_2 + a_3 - a_4 ... C=a1a2+a3a4...

我们可以任意删除某些位置的数字(删除后, 该位置后的数字会向前补位), 要求删除尽可能少的位置, 使得 C = 0 C = 0 C=0.
最后输出: 删除的位置数目, 以及具体删除的位置下标.

解题思路

思维

我们考虑 [ l , r ] [l, r] [l,r]如果本身就已经满足要求, 那么我们不需要删除.

反之, 如果 [ l , r ] [l, r] [l,r]奇数长度区间, 则我们至少需要删除一个位置, 才能满足题意. 若为偶数长度, 则我们至少需要删除两个位置.

最后C = 0, 表明剩余的数字一定得是偶数个. 因此偶数的情况我们至少要删除两次.


对于 [ l , r ] [l, r] [l,r] 删除一个位置 p o s pos pos, 则变化为 [ p o s + 1 , r ] [pos + 1, r] [pos+1,r]区间贡献变为原先的相反数.

我们考虑假设奇数长度区间 [ l , r ] [l, r] [l,r]此时 C = k ( k ≠ 0 ) C = k (k \ne 0) C=k(k=0), 我们应当找到 [ p o s + 1 , r ] [pos + 1, r] [pos+1,r]区间, 使得该区间的贡献为 ⌊ k 2 ⌋ \lfloor \frac k2 \rfloor 2k. 这样我们删除位置 p o s pos pos, 即可使得 C = 0 C = 0 C=0.

对于偶数长度区间 [ l , r ] [l, r] [l,r], 我们可以删除左端点 或 右端点, 转化为奇数长度区间情况即可.


考虑如何找到 p o s pos pos?
由上述分析, 我们相当于把 [ l , r ] [l, r] [l,r] p o s pos pos位置分隔开, 使得 [ l , p o s − 1 ] [l, pos - 1] [l,pos1] [ p o s + 1 , r ] [pos + 1, r] [pos+1,r]产生的贡献相互抵消掉.

如果用 s [ ] s[] s[]记录每个位置贡献的前缀和. 则有公式 s [ r ] − s [ p o s ] = s [ p o s − 1 ] − s [ l − 1 ] s[r] - s[pos] = s[pos - 1] - s[l - 1] s[r]s[pos]=s[pos1]s[l1]. 变形得: s [ l − 1 ] + s [ r ] = s [ p o s − 1 ] + s [ p o s ] s[l - 1] + s[r] = s[pos - 1] + s[pos] s[l1]+s[r]=s[pos1]+s[pos].

因此我们记录 s [ i − 1 ] + s [ i ] s[i - 1] + s[i] s[i1]+s[i]的值即可.

前缀数组 s [ ] s[] s[]记录的是从第一个位置开始的情况, 为什么可以保证公式的正确性呢?

如果当前区间与从1开始的情况不一样. 即: l l l位置是记录的是负号的情况, 而 [ l , r ] [l, r] [l,r]中是正号的情况. 那么前缀和的改变为: 变成原先的相反数, 对于后续的区间同样如此, 就相当于我们在等式两边加上了负号, 是不影响结果的.

p o s pos pos位置一定存在于 [ l , r ] [l, r] [l,r]中吗?

一定. 对于 [ l , r ] [l, r] [l,r], 当我们计算到 r r r位置时, C = k C = k C=k. 由于我们从左向右进行计算时, 每个位置只会改变 ± 1 \pm1 ±1, 而 p o s − 1 pos - 1 pos1位置应该产生了 ⌊ k 2 ⌋ \lfloor \frac k2 \rfloor 2k的贡献. 因此 p o s ∈ [ l , r ] pos \in [l, r] pos[l,r].

AC代码

#include <bits/stdc++.h>
#define rep(i, n) for (int i = 1; i <= (n); ++i)
using namespace std;
typedef long long ll;
const int N = 3E5 + 10;
char a[N];
int s[N];
int main()
{
    int t; cin >> t;
    while (t--) {
    	int n, m; scanf("%d %d", &n, &m);
    	scanf("%s", a + 1);

    	unordered_map<int, set<int>> st;
    	rep(i, n) {
    		s[i] = s[i - 1] + (a[i] == '+' ? 1 : -1) * (i & 1 ? 1 : -1);
			int qaq = s[i] + s[i - 1];
			st[qaq].insert(i);
    	}

    	auto fact = [&](int l, int r) {
    		int qaq = s[l - 1] + s[r];
    		return *st[qaq].lower_bound(l);
    	};

    	rep(i, m) {
    		int l, r; scanf("%d %d", &l, &r);
    		if (s[l - 1] == s[r]) puts("0");
    		else {
    			if ((r - l + 1) & 1) printf("1\n%d\n", fact(l, r));
    			else printf("2\n%d %d\n", r, fact(l, r - 1));
    		}
    	}
    }

    return 0;
}

END

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

逍遥Fau

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值