Educational Codeforces Round 10 C. Foe Pairs

题目链接

题记

真正的水题,然而排位赛的时候不会。这个能算是什么策略呢,按左端点分类,枚举所有区间,维护右区间的最小值就行了。

题意

给出一个排列数组p,包含1到n的数。现在给出一些数对,求不包含这些成对数字的区间有多少。比如p = [1, 3, 2, 4],给出数对(3, 2), (4, 2),则区间(2, 3)包含了数对(3, 2),不可行;区间(4, 2)包含了数对(3, 2)和(4, 2),不可行。区间(1, 2)是可行的。另外,区间长度可以为1,4个单独的区间也是可行的。这就是第一组样例答案为5的分析。

思路

看官方题解得到的思路。

这个最后的处理方法其实和 这道题 有相似的地方,相当于将问题分类成每一个左端点的情况。而这一道题,从右到左枚举每个左端点lf,只要维护一个值rg,使得rg是对应当前左端点能够取到的最大右端点值+1(或者说是不能取到的最小右端点值)。这个左端点能够产生的所有区间数量就是rg  - lf。

我们来考虑为什么可以这样得到答案。如果区间i1是不可行的,而区间i2包含区间i1,那么区间i2一定是不可行的。这样就保证了对于lf,大于等于rg的右端点一定不构成合法区间,而当我们把lf向左移动的时候,rg只能变小,不可能变大。

如果区间i2是可行的,而区间i2包含区间i1,那么区间i1一定也是可行的。既然如此,当我们向左移动lf的时候,rg只要不向右移动,那么在左移lf之前的所有约束就可以一并满足,新增加的约束只是当前的lf这个位置所产生的。为了满足它,只需要将rg的取值更新为min(rg, 当前lf位置的点所处的点对中,在lf右边且位置最靠左的位置)。

所以我们预处理的时候,先处理出每一个位置的点a,对于所有点对(a, b),找到那个b的位置大于a,且b最小的位置,把这个位置存在数组中,维护rg的时候使用即可。

代码

#include<bits/stdc++.h>
#define ll long long
using namespace std;

const int max_n = 3e5 + 50;
int p[max_n];
int n, m;
int max_right[max_n];
int pos[max_n];

int main() {
	//输入n m 初始化max_right为n
	scanf("%d %d", &n, &m);
	for (int i = 1; i <= n; i++) {
		max_right[i] = n + 1;
	}

	//输入p序列,处理出每一个元素对应的位置
	for (int i = 1; i <= n; i++) {
		scanf("%d", &p[i]);
		pos[p[i]] = i;
	}

	//输入点对,记录出每一个位置的max_right
	for (int i = 1; i <= m; i++) {
		int x, y;
		scanf("%d %d", &x, &y); 
		x = pos[x], y = pos[y];
		if(x > y) swap(x, y);
		max_right[x] = min(max_right[x], y);
	}

	//从右到左遍历,维护当前左端点对应右端点的最大值,计数答案 rg是记录距离lf最近的右边不能包括进的点
	int rg = n + 1;
	ll ans = 0;
	for (int lf = n; lf >= 1; lf--) {
		rg = min(rg, max_right[lf]);
		ans += rg - lf;
	}
	cout << ans << endl;
	return 0;
}

反思

关于区间问题,这样枚举左端点的形式,建议自己可以在没有思路的时候尝试往这方面想想。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值