第七届蓝桥杯本科C/C++ B组 第五题 题解 广场舞(离散化) 可过100%测试数据

转载注明出处第七届蓝桥杯本科C/C++ B组 第五题 题解 广场舞(离散化) 可过100%测试数据_QNU_Yt的博客-CSDN博客

描述

LQ市的市民广场是一个多边形,广场上铺满了大理石的地板砖。

地板砖铺得方方正正,就像坐标轴纸一样。

以某四块砖相接的点为原点,地板砖的两条边为两个正方向,一块砖的边长为横纵坐标的单位长度,则所有横纵坐标都为整数的点都是四块砖的交点(如果在广场内)。

广场的砖单调无趣,却给跳广场舞的市民们提供了绝佳的参照物。每天傍晚,都会有大批市民前来跳舞。

舞者每次都会选一块完整的砖来跳舞,两个人不会选择同一块砖,如果一块砖在广场边上导致缺角或者边不完整,则没人会选这块砖。

(广场形状的例子参考【图1.png】)

图1.png

现在,告诉你广场的形状,请帮LQ市的市长计算一下,同一时刻最多有多少市民可以在广场跳舞。

输入

输入的第一行包含一个整数n,表示广场是n边形的(因此有n个顶点)。

接下来n行,每行两个整数,依次表示n边形每个顶点的坐标(也就是说广场边缘拐弯的地方都在砖的顶角上。

数据保证广场是一个简单多边形。

输出

输出一个整数,表示最多有多少市民可以在广场跳舞。

输入样例 

5
3 3
6 4
4 1
1 -1
0 4

输出样例 

7

提示

【样例说明】

  • 广场如图1.png所示,一共有7块完整的地板砖,因此最多能有7位市民一起跳舞。

【数据规模与约定】

  • 对于30%的数据,n不超过100,横纵坐标的绝对值均不超过100。
  • 对于50%的数据,n不超过1000,横纵坐标的绝对值均不超过1000。
  • 对于100%的数据,n不超过1000,横纵坐标的绝对值均不超过100000000(一亿)。

【资源约定】

  • 峰值内存消耗 < 256M
  • CPU消耗  < 1000ms

【注意】

  • 请严格按要求输出,不要画蛇添足地打印类似:“请您输入...” 的多余内容。
  • 所有代码放在同一个源文件中,调试通过后,拷贝提交该源码。
  • 注意: main函数需要返回0
  • 注意: 只使用ANSI C/ANSI C++ 标准,不要调用依赖于编译环境或操作系统的特殊函数。
  • 注意: 所有依赖的函数必须明确地在源文件中 #include <xxx>, 不能通过工程设置而省略常用头文件。
  • 提交时,注意选择所期望的编译器类型。

关键位置都标明注释了,希望能帮到更多像我一样的初学者。(大佬勿喷)

话不多说,上代码:

#include <bits/stdc++.h>
using namespace std;
const int maxn = 10010;
const double eps = 1e-8;
typedef long long LL;

int sgn(double x) {
	if(fabs(x) < eps)
		return 0;
	if(x < 0)
		return -1;
	else
		return 1;
}

struct Point {
	LL x, y;
	Point(LL x = 0, LL y = 0): x(x), y(y) {}
	bool operator <(const Point &ano)const {
		if(x == ano.x)
			return y < ano.y;
		return x < ano.x;
	}
	void input() {
		scanf("%lld %lld", &x, &y);
	}
} P[maxn];

struct Line {
	Point s, e;
	LL a, b, c;   // 直线方程中 a/c 为斜率, b/c 为纵截距
	bool k;  // 可认为标志区间哪边矮
	Line(Point s = Point(), Point e = Point()): s(s), e(e) {}
} L[maxn];

/**
    在一段区间内,按线段中点纵坐标从小到大排序
 */
struct PosOfLine {
	double midy;
	int id;
	PosOfLine(double midy = 0, int id = 0): midy(midy), id(id) {}
	bool operator <(const PosOfLine &ano)const {
		if(sgn(midy - ano.midy) == 0)
			return id < ano.id;
		return sgn(midy - ano.midy) < 0;
	}
} pos[maxn];  /// 每个区间内都不会超过n条线段

int X[maxn];
int n;

LL gcd(LL a, LL b) {
	return b ? gcd(b, a % b) : a;
}

/**
    计算 sum((LL)floor((a*i+b)*1.0/c),0<=i<n)
 */
LL count(LL a, LL b, LL c, LL n) {
	if(a < 0)
		return count(-a, b + a * (n - 1), c, n);
	if(n == 0)
		return 0;
	LL ans = 0;
	if(a >= c)
		ans += n * (n - 1) / 2 * (a / c);
	a %= c;
	if((b < 0 ? -b : b) >= c)
		ans += n * (b / c);
	b %= c;
	if(a == 0)
		return ans;
	ans += count(c, (a * n + b) % c, a, (a * n + b) / c);
	return ans;
}

LL bub(Line l, int left, int right) {
	LL ans = count(l.a, l.b, l.c, right + 1);
	ans -= count(l.a, l.b, l.c, left);
	return ans;
}

LL blb(Line l, int left, int right) {
	LL ans = count(l.a, l.b + l.c - 1, l.c, right + 1);
	ans -= count(l.a, l.b + l.c - 1, l.c, left);
	return ans;
}

bool check(Line l0, Line l1, int left, int right) {
	LL a1 = ((LL)l0.a * left + l0.b) / l0.c;
	LL a2 = ((LL)l1.a * right + l1.b) / l1.c;
	if(a1 <= a2)
		return 1;
	if(a1 - a2 > 1)
		return 0;
	int a3 = ((LL)l0.a * left + l0.b) % l0.c;
	int a4 = ((LL)l1.a * right + l1.b) % l1.c;
	return ((LL)a3 * l1.c - (LL)a4 * l0.c < 0);
}

LL solve(Line &up_line, Line &down_line, int left, int right) {
    // 去掉左边上边界下方的方格数小于等于下边界下方的方格数的情况 , 说明没有完整的格子
	if(check(up_line, down_line, left + up_line.k, left + !down_line.k)) {
		int ll = left - 1, rr = right + 1;
		while(ll + 1 < rr) {
			int mid = (ll + rr) / 2;
			if(check(up_line, down_line, mid + up_line.k, mid + !down_line.k))
				ll = mid;
			else
				rr = mid;
		}
		left = rr;
		if(left > right)
			return 0;
	}
	// 去掉右边上边界下方的方格数小于等于下边界下方的方格数的情况 , 说明没有完整的格子
	if(check(up_line, down_line, right - !up_line.k, right - down_line.k)) {
		int ll = left - 1, rr = right + 1;
		while(ll + 1 < rr) {
			int mid = (ll + rr) / 2;
			if(check(up_line, down_line, mid - !up_line.k, mid - down_line.k))
				rr = mid;
			else
				ll = mid;
		}
		right = ll;
		if(left > right)
			return 0;
	}
	LL ans = 0;
	// 计算上边界下方方格数
	if(up_line.s.y < up_line.e.y)
		ans += bub(up_line, left, right - 1);  // 斜率大于0, 左边的格子矮,所以区间右端点减一
	else
		ans += bub(up_line, left + 1, right);  // 斜率小于0, 右边的格子矮,所以区间左端点减一
    // 计算下边界下方方格数
	if(down_line.s.y < down_line.e.y)
		ans -= blb(down_line, left + 1, right);
	else
		ans -= blb(down_line, left, right - 1);
	return ans;
}

int main() {
	scanf("%d", &n);
	for(int i = 0; i < n; ++i) {
		P[i].input();
		X[i] = P[i].x;
	}
	for(int i = 0; i < n; ++i) {
		L[i] = Line(P[i], P[(i + 1) % n]);
		if(L[i].s.x > L[i].e.x)
			swap(L[i].s, L[i].e);
		LL t_a = L[i].e.y - L[i].s.y, t_b = L[i].e.x - L[i].s.x;
		LL t_c = gcd(t_a, t_b);
		t_b /= t_c;
		t_a /= t_c;
		if(t_b < 0)  // t_b本来肯定为正,若现在为负说明t_c为负,那么t_a为负
			t_b *= -1, t_a *= -1;
		L[i].a = t_a;
		L[i].c = t_b;
		L[i].b = t_b * L[i].s.y - t_a * L[i].s.x;
		L[i].k = L[i].s.y > L[i].e.y;
	}
	LL ans = 0;
	sort(X, X + n);  // 离散化
	for(int i = 0; i < n - 1; ++i) {  // 枚举所有段
		if(X[i] ^ X[i + 1]) {
			int cnt = 0;
			for(int j = 0; j < n; ++j) {  // 枚举多边形所有边
				if(L[j].s.x <= X[i] && L[j].e.x >= X[i + 1]) { // 保证L[j]有一部分在区间[X[i], X[i+1]]
					pos[cnt++] = PosOfLine((double)(L[j].a * (double)(X[i] + X[i + 1]) / 2.0 + L[j].b) / L[j].c, j);
				}
			}
			sort(pos, pos + cnt);  // cnt一定为偶数
			for(int j = 0; j < cnt; j += 2) {
				ans += solve(L[pos[j + 1].id], L[pos[j].id], X[i], X[i + 1]);;
			}
		}
	}
	cout << ans << endl;
	return 0;
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
蓝桥杯是一个国内著名的计算机比赛,为了帮助参赛者更好地准备和了解比赛的型,组委会会公布历年的真并提供相应的题解。 首先,我们需要了解蓝桥杯是一个综合性的计算机比赛,测试的对象包括计算机基础知识、编程能力以及解决实际问的能力。 在历年的真中,参赛者将面临不同类型的目,包括算法设计与优数据结构与算法问、编程等。其中针对Python B组的目主要考察的是对Python语言的掌握和应用能力。 目解答一般会包含以下几个方面的内容: 1. 目分析与理解:读取目,理解目的要求和限制条件。通过仔细分析目,确定目的输入与输出,以及问的核心。 2. 设计解决方案:根据目要求和限制条件,设计一个合适的解决方案。可以使用合适的算法和数据结构来解决问,并做出相应的性能优。 3. 编写代码实现:根据设计的方案编写相应的代码实现。需要注意的是,Python语言有其独特的语法和特性,掌握好这些特性可以更好地完成编程任务。 4. 调试与测试:编写完代码后,需要进行调试和测试。通过运行样例输入和输出,检查代码是否符合目要求,并且没有逻辑上的错误。 5. 总结与优:在完成目解答后,可以进行总结和优。包括分析算法复杂度、代码风格和可读性等方面,以便在比赛中更好地表现。 在准备蓝桥杯时,可以通过阅读历年的真题解来了解比赛的难度和类型,针对性地进行练习和提高。同时也可以参加相关的培训班和讨论活动,与其他参赛者交流经验和技巧。 总而言之,历年蓝桥杯的解答对于提高自己的编程能力和应对比赛非常有帮助。通过认真分析和实践,可以更好地理解并掌握Python编程,并在比赛中取得更好的成绩。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值