精確狙擊

小蔥同學今天學會了複製,於是我們現在有N個小蔥同學。N個小蔥同學每人有一把彈弓,並且第i個小蔥同學站在(\(x_i, y_i\))的位置上。現在所有的小蔥同學希望把彈弓的射程調到某一個值,使得在這個射程下,存在M個小蔥同學,這M個小蔥同學之之間任意兩位都可以互相射擊到。問這個距離最小是多少。

【輸入格式】

第一行兩個數N,M

接下來N行每行兩個整數代表小蔥同學的座標

【輸出格式】

輸出一行一個六位小數代表答案

【樣例】

4 3

0 0

0 1

1 1

1 0

【輸出】

1.414212

【數據規模與約定】

對於30%的數據,\(N \leq 10\)

對於另外10%的數據,\(M = 2\)

對於另外20%的數據,\(M = 3\)

對於80%的數據,\(M \leq 50\)

對於100%的數據,\(1 \leq M \leq N \leq 200, |x|, |y| \leq 10^4\)

Solution

看題意似乎是讓我們求一個最大團,但這在普通圖裏是個NPC問題,那有什麼特殊圖能做嗎?

二分圖可以。因爲二分圖相當於兩個點集,每個點集之內的點相互之間都有邊,兩個點集之間又有一些邊。所有就相當於要找一個最大匹配。這個顯然可以求。

但是,哪裏來的二分圖呢?

考慮這是一個座標圖,任意兩點的連線會有上面的一部分和下面的一部分。當我們選中兩個點,並以兩個點的距離爲半徑畫圓的時候,這兩個圓會有一個相交的集合。將這個集合分成兩半,一半在兩點連線的上面,一半在下面。這兩個點集的內部的點的距離一定小於等於選中兩點之間的距離,而兩個點集之間的點不一定。所以這就形成了一個二分圖。

至於怎麼判斷某個點是屬於上面一個集合還是下面的,用向量的叉乘就好了。

所有,我們首先二分一個距離,然後找到所有滿足距離小於等於這個距離的點對,再以每個點對畫圓,構建二分圖,統計每個二分圖的最大匹配。如果這個匹配大於等於M,就說明OK,反之不行。

這裏要注意一點,二分圖不能直接求最大匹配,因爲我們實際上要求的是一個最大團,所以要求一個反圖的最大匹配。最大團 = 反圖最大獨立集 = 總點數 - 反圖最大匹配

這題就這樣做完了qwq

#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
using namespace std;
int n, m, l, r, p[2][210], re[210], x[210], y[210];
bool use[210];
int c, dis[210][210], d[210 * 210];
inline int cross(int x1, int y1, int x2, int y2) {//叉乘
  return x1 * y2 - x2 * y1;
}
inline bool dfs(int now) {//求最大匹配
  for (int i = 1; i <= r; ++i)
    if (!use[i] && dis[p[0][now]][p[1][i]] > c) {//因爲是反圖,所以找距離大於c的
      use[i] = 1;
      if (!re[i] || dfs(re[i])) {
        re[i] = now; return 1;
      }
    }
  return 0;
}
inline bool check(int d) {
  if (m == 1) return 1;
  for (int i = 1; i <= n; ++i)
    for (int j = i + 1; j <= n; ++j)
      if (dis[i][j] <= d) {
        if (m == 2) return 1;
        l = 0, r = 0;
        for (int k = 1; k <= n; ++k)
          if (k != i && k != j && dis[i][k] <= dis[i][j] && dis[j][k] <= dis[i][j]) {
              //判斷這個節點是否在兩個圓的交集內,並劃分集合
            if (cross(x[k] - x[i], y[k] - y[i], x[j] - x[i], y[j] - y[i]) <= 0) p[0][++l] = k;
            else p[1][++r] = k;
          }
        c = d;
        memset(re, 0, sizeof re);
        int ans = 0;
        for (int k = 1; k <= l; ++k) {//求匹配
          memset(use, 0, sizeof use);
          if (dfs(k)) ans++;
        }
        ans = l + r - ans + 2;//統計答案
        if (ans >= m) return 1;
      }
  return 0;
}
int main() {
  freopen("die.in", "r", stdin);
  freopen("die.out", "w", stdout);
  scanf("%d%d", &n, &m);
  if (m == 1) {
    printf("0.000000\n"); return 0;
  }
  for (int i = 1; i <= n; ++i)
    scanf("%d%d", &x[i], &y[i]);
  int cnt = 0;
  for (int i = 1; i <= n; ++i)
    for (int j = 1; j <= n; ++j) {//計算出兩點之間的距離
      dis[i][j] = (x[i] - x[j]) * (x[i] - x[j]) + (y[i] - y[j]) * (y[i] - y[j]);
        //直接存儲平方
      d[++cnt] = dis[i][j];//二分的數組
    }
  sort(d + 1, d + 1 + cnt);
  int l = 0, r = cnt, u = cnt;
  while (l < r) {
    int mid = (l + r) >> 1;
    if (check(d[mid])) u = mid, r = mid - 1;
    else l = mid + 1;
  }
  printf("%.6lf\n", sqrt(d[u]));
  return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章