Codeforces Edu 87 题解

Codeforces Edu 87

C. Simple Polygon Embedding【三分+计算几何】

题目

对于一个边长为1的正 2 n 2n 2n边形,求其最小覆盖正方形的边长

题解

如果 n n n为偶数,那么该正多边形可以旋转成如下形式

在这里插入图片描述

其中四条边与正方形边界平行,因此正方形的边长计算公式为
a n s = 1 t a n π 2 n ans=\frac{1}{tan\frac{\pi}{2n}} ans=tan2nπ1
而如果 n n n为奇数,不论怎么旋转最多只有两条边与正方形边界平行

在这里插入图片描述

因此,可以用三分的方法在旋转过程中找到一个最小覆盖正方形。根据对称性,旋转角度只需要在 0 ∼ π 2 n 0\sim \frac{\pi}{2n} 02nπ。实际上,由对称性不难猜出,应该在旋转的中间位置,即 π 4 n \frac{\pi}{4n} 4nπ取到最小值,因此公式为
a n s = cos ⁡ ( π 4 n ) sin ⁡ ( π 2 n ) ans=\frac{\cos(\frac{\pi}{4n})}{\sin(\frac{\pi}{2n})} ans=sin(2nπ)cos(4nπ)
这里给出 n n n为奇数时用三分的方法寻找最小值的过程

在这里插入图片描述

用两个向量X,Y追踪旋转过程中最小覆盖正方形边长的变化,正方形的边长就等于 m a x ( X 向 量 在 x 轴 方 向 投 影 , Y 向 量 在 y 轴 方 向 投 影 ) ∗ 2 max(X向量在x轴方向投影,Y向量在y轴方向投影)*2 max(Xx,Yy)2

题解

#include <bits/stdc++.h>
using namespace std;
#define mem(a,b) memset(a,b,sizeof(a))
#define endl '\n'
#define pi acos(-1)
typedef long long ll;
const int INF = 1 << 30;
const double eps = 1e-8;
struct Point {	// 点类
	double x, y;
	Point() {};
	Point(double x, double y): x(x), y(y) {}
	void operator=(const Point&a) {
		x = a.x, y = a.y;
	}
};
typedef Point Vector;
double Dot(Vector A, Vector B) {return A.x * B.x + A.y * B.y;}
double Cross(Vector A, Vector B) {return A.x * B.y - A.y * B.x;}
double Len(Vector A) {return sqrt(Dot(A, A));}	// 向量长度
Vector Rotate(Vector A, double rad)
// 向量逆时针旋转
{
	return Vector(A.x * cos(rad) - A.y * sin(rad), A.x * sin(rad) + A.y * cos(rad));
}

int n;
Vector X, Y; // 分别监测旋转过程中x,y的最小最大值

double cal(double rad)
// 计算旋转后最小正方形边长
{
	Vector tempx = Rotate(X, rad);
	Vector tempy = Rotate(Y, rad);
	return max(tempx.x, tempy.y) * 2;
}
double tsearch(double left, double right)
{
	double ans = INF;
	double mid, midmid;
	while (right - left > eps) {
		mid = (left + right) / 2;
		midmid = (mid + right) / 2;
		double ans1 = cal(mid), ans2 = cal(midmid);
		if (ans1 < ans2) {
			right = midmid;
			ans = min(ans, ans1);
		}
		else {
			left = mid;
			ans = min(ans, ans2);
		}
	}
	return ans;
}
int main()
{
	ios::sync_with_stdio(0), cin.tie(0);
	int t; cin >> t;
	while (t--) {
		cin >> n;
		X.x = 0.5 / sin(pi / (2 * n)), X.y = 0;
		Y.x = 0.5, Y.y = 0.5 / tan(pi / (2 * n));
		double sta = 0, end = pi / (2 * n);	// 旋转范围
		cout << fixed << setprecision(8) << tsearch(sta, end) << endl;
	}
	return 0;
}

D. Multiset【二分答案】

题目

给定一个具有 n n n个元素的集合,有 q q q次操作,每次操作给定一个整数 k k k,规则如下:

  1. 如果 k ≥ 1 k\ge 1 k1,则把 k k k加入集合中;
  2. 如果 k < 0 k<0 k<0,则从集合中按升序排序后,移除第 ∣ k ∣ |k| k个元素

如果最终集合非空,输出任一集合中元素即可

  • Memory limit: 28mb

题解

本题可以选择用线段树之类的数据结构来模拟题目中的操作,但需要进行一定的优化,因为本题空间限制非常严格。

实际上,考虑到最终只需要输出集合中的任意一个元素即可,不妨假设要输出最小的一个,那么可以用二分答案的方法来查找。

二分查找的思路为:
定义函数count(x)表示:对于元素x,最终集合中小于等于x的元素个数。
最终答案就应该是找到一个最小的x满足 c o u n t ( x ) > 0 count(x)>0 count(x)>0

因为在本题中,集合中元素不唯一,并且不一定是连续的。
假设最终集合中最小的元素是y,那么在二分过程中,任何大于等于y的数返回结果都是 ≥ 1 \ge 1 1,而比y小的数返回结果都是0,因此y就是那个满足count(x)>0的最小元素

对于最终集合为空的情况,可以用一个非常大的数去检验。因为集合中元素 a [ i ] ≤ 1 0 6 a[i]\le 10^6 a[i]106,如果最终集合中小于等于 1 0 9 10^9 109的元素为0,就说明集合中不可能存在任何元素。(如果 c o u n t ( 1 e 9 ) = = 0 count(1e9)==0 count(1e9)==0,就说明最终集合中的最小元素必定是大于 1 e 9 1e9 1e9的,而这与集合中元素 ≤ 1 e 6 \le 1e6 1e6相矛盾)

if (count(int(1e9)) == 0) {
    cout << 0 << endl;
}

代码

#include <bits/stdc++.h>
using namespace std;
#define mem(a,b) memset(a,b,sizeof(a))
#define endl '\n'
typedef long long ll;
const int maxn = 1e6 + 10;
int n, q;
vector<int>a, k;
int count(int x)
// 计算最终集合里面小于等于x的元素个数
{
	int cnt = 0;
	for (int i = 0; i < n; i++) {	// 初始化
		if (a[i] <= x) cnt++;
	}
	for (int i = 0; i < q; i++) {
		if (k[i] > 0 && k[i] <= x) cnt++;	// 插入正数,并且比x小,必然会排在x前面
		if (k[i] < 0 && abs(k[i]) <= cnt) cnt--;	// 插入负数,如果其绝对值<=cnt,就会删除x左侧的元素
	}
	return cnt;
}
int main()
{
	ios::sync_with_stdio(0), cin.tie(0);
	cin >> n >> q;
	a.resize(n); k.resize(q);
	for (int i = 0; i < n; i++) {
		cin >> a[i];
	}
	for (int i = 0; i < q; i++) {
		cin >> k[i];
	}
	if (count(int(1e9)) == 0) {	// 判断最终集合是否为空
		cout << 0 << endl;
		return 0;
	}
	int left = 1, right = int(1e6) + 1;
	while (left < right) {
		int mid = left + (right - left) / 2;
		if (count(mid) <= 0) left = mid + 1;
		else right = mid;
	}
	cout << right << endl;
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值