链接:
题意:
平面上有 a a a 个红色点和 b b b 个蓝色点,没有三点共线,红蓝点之间可以连边,求一棵生成树,使得第 i i i 个蓝点的度数恰好为 r i r_i ri ,并且生成树的连边在平面上不会在除了端点的地方相交。
保证 ∑ r i = a + b − 1 , 1 ≤ r i ≤ a \sum r_i = a+b-1, 1\le r_i\le a ∑ri=a+b−1,1≤ri≤a 。
题解:
考虑用归纳法给出构造。当 max r i = 1 \max r_i = 1 maxri=1 时,构造显然。
否则,取出度数最大的点,用一条经过这个点的直线把平面划分成两部分,剩下的两部分递归做。
考虑如何去划分,记一个点的权值为 r i − 1 r_i - 1 ri−1 (我们认为红点的 r i r_i ri 是 0 0 0 ),那么其他点的权值和等于 − max r i -\max r_i −maxri 。我们需要把其他点分成两部分,使得每部分的权值和都小于等于 − 1 -1 −1 ,这样就能保证连通性。
对于一定有解的证明:记一个半平面的权值和为 s u m sum sum ,那么限制相当于 1 − max r i ≤ s u m ≤ − 1 1 - \max r_i\le sum\le -1 1−maxri≤sum≤−1 。随便画一条线,如果 s u m sum sum 满足要求了,就找到了一组解;否则将这条线转一圈, s u m sum sum 会变成 − s u m − max r i -sum - \max r_i −sum−maxri 。在变化的过程中, s u m sum sum 会从 < 1 − max r i < 1 - \max r_i <1−maxri 变成 > − 1 > -1 >−1 (或者反过来),而变化量不会超过 max r i − 1 \max r_i - 1 maxri−1 ,所以一定存在一个时刻 s u m sum sum 落在区间内部。
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long double ld;
const ld pi = acos(-1);
const ld eps = 1e-9;
struct point_t {
int x, y, id, degree;
point_t(int x = 0, int y = 0, int id = 0, int degree = 0):x(x), y(y), id(id), degree(degree) {
}
};
void solve(vector<point_t> points) {
int max_degree = 0, max_id = -1;
for (int i = 0; i < points.size(); ++i) {
if (max_degree < points[i].degree) {
max_degree = points[i].degree;
max_id = i;
}
}
if (max_degree == 1) {
for (int i = 0; i < points.size(); ++i) {
if (!points[i].degree) {
max_id = i;
}
}
for (int i = 0; i < points.size(); ++i) {
if (i != max_id) {
cout << points[i].id + 1 << " " << points[max_id].id + 1 << endl;
}
}
} else {
int n = points.size() - 1;
swap(points[max_id], points[n]);
vector<pair<ld, int>> events;
for (int i = 0; i < n; ++i) {
ld from = atan2(points[i].y - points[n].y, points[i].x - points[n].x);
ld to = from + pi;
if (to > pi) {
to -= pi * 2;
}
events.emplace_back(from, i);
events.emplace_back(to, i + n);
}
sort(events.begin(), events.end());
vector<bool> side(n);
int sum = 0;
for (auto p : events) {
if (p.first > eps && p.second < n) {
sum += points[p.second].degree - 1;
side[p.second] = true;
}
}
for (auto p : events) {
if (sum <= -1 && sum >= 1 - max_degree) {
break;
}
if (p.second < n) {
sum += points[p.second].degree - 1;
side[p.second] = true;
} else {
sum -= points[p.second - n].degree - 1;
side[p.second - n] = false;
}
}
vector<point_t> left, right;
for (int i = 0; i < n; ++i) {
if (side[i]) {
left.push_back(points[i]);
} else {
right.push_back(points[i]);
}
}
left.emplace_back(points[n].x, points[n].y, points[n].id, -sum);
right.emplace_back(points[n].x, points[n].y, points[n].id, sum + max_degree);
solve(left);
solve(right);
}
}
int main() {
#ifdef wxh010910
freopen("input.txt", "r", stdin);
#endif
int tt;
cin >> tt;
while (tt--) {
int n, m;
cin >> n >> m;
vector<int> degree(m);
for (int i = 0; i < m; ++i) {
cin >> degree[i];
}
vector<point_t> points(n + m);
for (int i = 0; i < n; ++i) {
cin >> points[i].x >> points[i].y;
points[i].id = i;
}
for (int i = 0; i < m; ++i) {
cin >> points[i + n].x >> points[i + n].y;
points[i + n].degree = degree[i];
points[i + n].id = i;
}
cout << "YES" << endl;
solve(points);
}
return 0;
}