CodeForces 1070 M. Algoland and Berland

链接:

link

题意:

平面上有 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+b1,1ria

题解:

考虑用归纳法给出构造。当 max ⁡ r i = 1 \max r_i = 1 maxri=1 时,构造显然。

否则,取出度数最大的点,用一条经过这个点的直线把平面划分成两部分,剩下的两部分递归做。

考虑如何去划分,记一个点的权值为 r i − 1 r_i - 1 ri1 (我们认为红点的 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 1maxrisum1 。随便画一条线,如果 s u m sum sum 满足要求了,就找到了一组解;否则将这条线转一圈, s u m sum sum 会变成 − s u m − max ⁡ r i -sum - \max r_i summaxri 。在变化的过程中, s u m sum sum 会从 &lt; 1 − max ⁡ r i &lt; 1 - \max r_i <1maxri 变成 &gt; − 1 &gt; -1 >1 (或者反过来),而变化量不会超过 max ⁡ r i − 1 \max r_i - 1 maxri1 ,所以一定存在一个时刻 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;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值