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}
0∼2nπ。实际上,由对称性不难猜出,应该在旋转的中间位置,即
π
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(X向量在x轴方向投影,Y向量在y轴方向投影)∗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,规则如下:
- 如果 k ≥ 1 k\ge 1 k≥1,则把 k k k加入集合中;
- 如果 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;
}