United Cows of Farmer John G
题目链接:luogu P7527
题目大意
给你一个序列,问你有多少个区间,满足区间最旁边的两个点的权值只在这个区间中出现一次。
思路
由于在比赛时的我比较神奇,我想不到线段树做法,反而用了个主席树。
所以不难看出我们可以预处理出一个东西,就是如果选一个点另一个点的范围。
你就设
l
s
t
i
lst_i
lsti 为
i
i
i 最后出现的位置,然后搞一搞就有了。
然后你不难想到你可以枚举右边的点,然后再它可以往左的范围中,看有多少个点往右的范围大于了这个点。
就主席树就是第
i
i
i 个树是
1
∼
i
1\sim i
1∼i 的数向右的贡献。
然后你每次就查询,然后放进树中就可以,然后你要选的数有范围就看树,选两个树前缀和的方式减一下就有了。
然后其实完全没有必要用主席树。
因为你可以可以这么想,你对于每个数字,只有最后出现的那个可以用。
那你出现一个就把原来旧的那个贡献改为
0
0
0,然后查询可以的区间里有多少个
1
1
1,然后新的改为
1
1
1。
然后即可以了。(这个做法对做铂金的加强版其实很大启发,我的主席树就完全不知道要怎么搞了)
代码
#include<cstdio>
#define ll long long
using namespace std;
const int N = 200005;
struct ZXtree {//主席树
int ls[N << 6], rs[N << 6], tot;
int lzy[N << 6];
int copy(int now) {
tot++;
ls[tot] = ls[now]; rs[tot] = rs[now];
lzy[tot] = lzy[now];
return tot;
}
int query(int now, int l, int r, int pl) {
if (!now) return 0;
if (l == r) return lzy[now];
int mid = (l + r) >> 1;
if (pl <= mid) return lzy[now] + query(ls[now], l, mid, pl);
else return lzy[now] + query(rs[now], mid + 1, r, pl);
}
int insert(int now, int l, int r, int L, int R) {
now = copy(now);
if (L <= l && r <= R) {
lzy[now]++;
return now;
}
int mid = (l + r) >> 1;
if (L <= mid) ls[now] = insert(ls[now], l, mid, L, R);
if (mid < R) rs[now] = insert(rs[now], mid + 1, r, L, R);
return now;
}
}T;
int n, b[N], lst[N];
int tm[N], aft[N];
int rt[N];
ll ans;
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i++) scanf("%d", &b[i]);
for (int i = 1; i <= n; i++) {
lst[i] = tm[b[i]];
tm[b[i]] = i;
}
for (int i = 1; i <= n; i++) tm[i] = n + 1;
for (int i = n; i >= 1; i--) {
aft[i] = tm[b[i]];
tm[b[i]] = i;
}
for (int i = 1; i <= n; i++) {
ans += 1ll * (T.query(rt[i - 1], 1, n, i) - T.query(rt[lst[i]], 1, n, i));//按这个区间能有的前面的位置看有多少个可以用
rt[i] = T.insert(rt[i - 1], 1, n, 1, aft[i] - 1);//把能有的区间直接丢进去
}
printf("%lld", ans);
return 0;
}