【luogu P7527】United Cows of Farmer John G(主席树)

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 1i 的数向右的贡献。
然后你每次就查询,然后放进树中就可以,然后你要选的数有范围就看树,选两个树前缀和的方式减一下就有了。

然后其实完全没有必要用主席树。
因为你可以可以这么想,你对于每个数字,只有最后出现的那个可以用。
那你出现一个就把原来旧的那个贡献改为 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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值