传送门
废话一波:这道题是几个月前比赛的题,当时就我一个人开了这道题,我当时写的可持久化线段树,但是思维出错了,一直WA。
这道题还可以用树状数组或者线段树来写,但是我想不到。。。。
出题人博客:https://blog.csdn.net/swust5120166213/article/details/86703350
解题思路:一对华农兄弟的条件就是两个人均在对方的防御范围内。我们可以认为为华农兄弟的右兄弟,然后我们去寻找他的左兄弟。如果必须是一对华农兄弟,则必须满足条件:。
我们列举出一种比较极端的情况就比较好理解了。
4
4 3 2 4
由上面的例子可以知道,4(右)的防御范围极限就在4(左)的位置。越靠近4(右),华农左兄弟的防御值就可以减少一。
那么可以发现 3 比 4(左) 小1,但是下标却大 1 。4(左)+1(下标)=3+2(下标)=2+3(下边)>=4(右)(下标)+1。
由特殊情况我们可以发现其他情况都是如此。
这样可以知道,我们只需要找区间内大于等于 x 的个数,直接可持久化线段树套模板即可。
坑点:答案记得用 long long,不能离散化,防御范围可能会越界
代码:
#include<bits/stdc++.h>
using namespace std;
const int maxn = 2e5 + 5;
int num[maxn], n, root[maxn], times;
long long ans = 0;
struct node {
int ls, rs, cnt;
#define left(a, b) p[a].ls, p[b].ls, l, mid
#define right(a, b) p[a].rs, p[b].rs, mid + 1, r
} p[maxn * 40];
void insert(int &now, int old, int l, int r, int x) {
now = ++times, p[now] = p[old], p[now].cnt++;
if (l == r) return;
int mid = (l + r) >> 1;
if (x <= mid) insert(left(now, old), x);
else insert(right(now, old), x);
}
int query(int sta, int end, int l, int r, int x) {
if (l >= x) return p[end].cnt - p[sta].cnt;
int mid = (l + r) >> 1;
if (x <= mid) return query(left(sta, end), x) + p[p[end].rs].cnt - p[p[sta].rs].cnt;
else return query(right(sta, end), x);
}
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
scanf("%d", &num[i]), insert(root[i], root[i - 1], 1, maxn << 1, i + num[i]);
if (i - 1)ans = ans + query(root[max(i - num[i], 0)], root[i - 1], 1, maxn << 1, 1 + i);
}
printf("%lld\n", ans);
}