洛谷P8858折线-题解

折线

题目描述

平面直角坐标系的第一象限内有一块左下角为 ( 0 , 0 ) (0,0) (0,0) 右上角为 ( 1 0 100 , 1 0 100 ) (10^{100},10^{100}) (10100,10100) 的矩形区域,区域内有正偶数个整点,试求出这样一条从 ( 0 , 0 ) (0,0) (0,0) 出发,到 ( 1 0 100 , 1 0 100 ) (10^{100},10^{100}) (10100,10100) 的在区域内部的折线:

  • 折线的每一部分都平行于 x x x 轴或 y y y 轴。
  • 折线不能经过给定的整点。
  • 折线将整块区域分成包含给定整点个数相等的两块。
  • 折线拥有尽可能少的折点。

可以证明一定存在一条满足限制的折线,你只需要输出满足限制的折线的折点数即可。

注意折点的坐标可以不是整数。

输入格式

输入第一行一个正整数 T T T,表示数据组数。

接下来的每组数据中,第一行一个正偶数 n n n,表示给定的整点个数。

接下来 n n n 行,第 i i i 行两个正整数 x i , y i x_i,y_i xi,yi,表示给定的第 i i i 个整点的坐标为 ( x i , y i ) (x_i,y_i) (xi,yi)

输出格式

输出 T T T 行,每行一个正整数,表示满足限制的折线的折点数。

样例 #1

样例输入 #1

3
4
1 1
1 2
4 1
4 2
6
1 2
1 3
2 1
2 2
2 3
3 2
12
1 3
2 2
2 3
2 4
3 1
3 2
3 4
3 5
4 2
4 3
4 4
5 3

样例输出 #1

2
3
4

提示

【样例解释】

对于第一组数据,一条合法的折线为: ( 0 , 0 ) → ( 2.5 , 0 ) → ( 2.5 , 1 0 100 ) → ( 1 0 100 , 1 0 100 ) (0,0) \to (2.5,0) \to (2.5,10^{100}) \to (10^{100},10^{100}) (0,0)(2.5,0)(2.5,10100)(10100,10100),它有 ( 2.5 , 0 ) (2.5,0) (2.5,0) ( 2.5 , 1 0 100 ) (2.5,10^{100}) (2.5,10100) 两个折点。

【数据范围】
测试点编号 n ≤ n \leq n特殊限制
1 ∼ 2 1 \sim 2 12 4 4 4
3 ∼ 4 3 \sim 4 34 10 10 10
5 ∼ 6 5 \sim 6 56 50 50 50
7 ∼ 8 7 \sim 8 78 1 0 5 10^5 105保证答案不大于 3 3 3
9 ∼ 10 9 \sim 10 910 1 0 5 10^5 105

对于所有数据, 1 ≤ T ≤ 1 0 4 , 1 ≤ ∑ n ≤ 5 × 1 0 5 , 1 ≤ x i , y i ≤ n 1 \leq T \leq 10^4, 1 \leq \sum n \leq 5 \times 10^5, 1 \leq x_i,y_i \leq n 1T104,1n5×105,1xi,yin,保证 n n n 为正偶数,每组数据中不存在两个坐标相同的整点。

题解

*引理1:*折点数量在 [ 2 , 4 ] [2,4] [2,4]之间

证明:(从x轴考虑,y轴同理)

考虑找到一个 a a a使得 x x x坐标小于 a a a的和 x x x坐标大于 a a a的点的数量不超过 n 2 \frac{n}{2} 2n(先预处理每个 x x x坐标的点的数量,再做一遍前缀和即可求出)

那么考虑将直线 x = a x=a x=a划分为两个部分,自底向上最多折3次即可达到划分目的,最后一次是当折线的坐标到了 ( a , 1 0 100 ) (a,10^{100}) (a,10100)的时候再折到终点,故上界是4

而需要划分点,至少需要折一次,然后还得再折一次到终点,故下界是2

引理2:

折两次的充要条件为存在一个 a a a,使得 x x x坐标小于 a a a y y y坐标小于 a a a的点的数量为 n 2 \frac{n}{2} 2n,证明显然

引理3

折3次的充要条件为存在一个矩形 ( 1 , i , i , n ) (1,i,i,n) (1,i,i,n) ( i , 1 , n , j ) (i,1,n,j) (i,1,n,j)使得矩形值为 n / 2 n/2 n/2

因为折两次只有两种图形(如下),证明显然

在这里插入图片描述

所以问题就变成了判断是否存在矩形 ( 1 , i , j , n ) (1,i,j,n) (1,i,j,n) ( i , 1 , n , j ) (i,1,n,j) (i,1,n,j)使得矩形值为 n / 2 n/2 n/2,采用悬线法解决

可以这样考虑,因为折三次可以分为从x折第一次还是从y折,下面讨论从 x x x折,处理矩形 ( i , 1 , n , j ) (i,1,n,j) (i,1,n,j)的方法

在这里插入图片描述

代码应该比较好懂,没看懂可以私信我

//fc D:\编程\C++\ex_line2.out D:\编程\C++\ex_line2.ans
#include<iostream>
#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
#define N 5000050
struct node {
	int x, y;
}a[N];
inline int read(void) {
	int x = 0, f = 1; char c = getchar();
	for (; !isdigit(c); c = getchar())
		if (c == '-') f = -1;
	for (; isdigit(c); c = getchar())
		x = x * 10 + c - '0';
	return x * f;
}
int f1[N], f2[N], g1[N], g2[N], n, m, q;
vector<int>s1[N], s2[N];
inline void init() {
	for (int i = 1; i <= n; i++)f1[i] = f2[i] = g1[i] = g2[i] = a[i].x = a[i].y = 0;
	for (int i = 1; i <= n; i++) {
		s1[i].clear();
		s2[i].clear();
	}
	n = read();
	for (int i = 1; i <= n; i++) {
		int x, y;
		x = read(); y = read();
		g1[x]++, g2[y]++;
		a[i].x = x, a[i].y = y;
		s1[x].push_back(i);
		s2[y].push_back(i);
	}
}
inline bool check2() {
	for (int i = 1, j = 0, k = 0; i <= n; i++) {
		j += g1[i];
		k += g2[i];
		if (j == n / 2 || k == n / 2)return true;
	}
	return false;
}
inline bool check3_1() {
	//第一部分,判断矩形(i,1,n,j)
	int l = 1, r = 1, m = 0;
	while (l <= n) {
		while (m < n / 2 && r <= n) {
			m += g2[r] - f2[r];
			//		printf("A %d %d %d\n", l, r, m);
			r++;
		}
		if (m == n / 2)return true;
		int len = s1[l].size();
		for (int i = 0; i < len; i++) {
			f2[a[s1[l][i]].y]++;
			if (a[s1[l][i]].y < r)m--;
		}
		l++;
	}
	if (m == n / 2)return true;
	return false;
}
inline bool check3_2() {
	int l = 1, r = 1, m = 0;
	while (l <= n) {
		while (m < n / 2 && r <= n) {
			m += g1[r] - f1[r];
			//		printf("A %d %d %d\n", l, r, m);
			r++;
		}
		if (m == n / 2)return true;
		int len = s2[l].size();
		for (int i = 0; i < len; i++) {
			f1[a[s2[l][i]].x]++;
			if (a[s2[l][i]].x < r)m--;
		}
		l++;
	}
	if (m == n / 2)return true;
	return false;
}
int main() {
	//	freopen("ex_line2.in","r",stdin);
	//	freopen("ex_line2.ans","w",stdout);
	int t = read();
	while (t--) {
		init();
		if (check2()) {
			puts("2");
			continue;
		}
		if (check3_1() || check3_2()) {
			puts("3");
			continue;
		}
		puts("4");
	}
}

最后附上一张可爱的图,希望开心

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值