「AcWing 374」「BZOJ 3035」導彈防禦塔
Description
Freda 的城堡遭受了 \(M\) 個入侵者的攻擊!
Freda 控制著 \(N\) 座導彈防禦塔,每座塔都有足夠數量的導彈,但是每次只能發射一枚。
在發射導彈時,導彈需要 \(T_1\) 秒才能從防禦塔中射出,而在發射導彈後,發射這枚導彈的防禦塔需要 \(T_2\) 分鐘來冷卻。
所有導彈都有相同的勻速飛行速度 \(V\),並且會沿著距離最短的路徑去打擊目標。
計算防禦塔到目標的距離 \(\text{Distance}\) 時,你只需要計算水平距離,而忽略導彈飛行的高度。
導彈在空中飛行的時間就是 (\(\text{Distance}/V\)) 分鐘,導彈到達目標後可以立即將它擊毀。
現在,給出 \(N\) 座導彈防禦塔的座標,\(M\) 個入侵者的座標,\(T_1,T_2\) 和 \(V\)。因為 Freda 的小夥伴 Rainbow 就要來拜訪城堡了,你需要求出至少多少分鐘才能擊退所有的入侵者。
Hint
- \(1\le N,M\le 50\);
- \(1\le T_1,T_2,V \le 2\times 10^3\);
- 座標絕對值不超過 \(10^4\)。
Solution
直接做不太好做,不如將問題轉化一下,判斷能否在時間 \(t\) 內擊退所有入侵者。
顯然如果在 \(t\) 時間內可以擊退,那麼 \(t + k(k>0)\) 的時間內也一定可以。滿足單調性,就可以 二分答案
考慮如果一個導彈防禦塔如果只能發射一枚導彈,那就是一個 二分圖最大匹配 問題。兩邊的點分別為防禦塔和入侵者。
如果可以發射不止一個,那就是 多重匹配問題。
多重匹配問題的常規解決方式之一是 拆點。
具體地,就是 \(t\) 時間內,若該防禦塔可以發射 \(x\) 個導彈,那麼就拆成 \(x\) 個點,然後使每一個入侵者的點向這些點連一條邊。
對於二分中某一個 \(t\) 的可行性的判斷,只要檢查 \(m\) 個入侵者的點是否都有導彈的點與之匹配即可。
也可以判斷最大匹配對數是否等於 \(m\)。
考慮到導彈拆點後點數多且不好控制,推薦從入侵者的點這一邊搜。
既減少了程式碼難度,也大大提高了程式碼執行效率(\(3266 \text{ms} \rightarrow 204 \text{ms}\)
Code
#include <cmath>
#include <cstdio>
#include <cstring>
#include <vector>
using namespace std;
const int N = 55;
const int S = 1e4 + 5;
const double eps = 1e-8;
int mis[2][N],inv[2][N];
int n,m,total;
double t1,t2,v;
double cost[N][N];
pair<int,double> extra[S];
#define sqr(x) ((x) * 1.0 * (x))
inline double dist(int x1,int y1,int x2,int y2) {
return sqrt(sqr(x1 - x2) + sqr(y1 - y2));
}
vector<int> G[N];
int match[S],vis[S];
void resetData() {
for (register int i = 1; i <= m; i++)
G[i].clear();
memset(match,sizeof match);
memset(vis,sizeof vis);
}
bool Dfs(int x,int t) {
for (vector<int>::iterator y = G[x].begin(); y != G[x].end(); y++)
if (vis[*y] != t) {
vis[*y] = t;
if(!match[*y] || Dfs(match[*y],t))
return match[*y] = x,true;
}
return false;
}
bool judge(double t) {
resetData();
for (register int i = 1; i <= m; i++)
for (register int j = 1; j <= total; j++)
if (extra[j].second + cost[extra[j].first][i] <= t)
G[i].push_back(j);
for (register int i = 1; i <= m; i++)
if (!Dfs(i,i)) return false;
return true;
}
void calcCost() {
for (register int i = 1; i <= n; i++)
for (register int j = 1; j <= m; j++)
cost[i][j] = dist(mis[0][i],mis[1][i],inv[0][j],inv[1][j]) / v;
total = n * m;
for (register int i = 1; i <= n; i++)
for (register int j = 1; j <= m; j++) {
int pos = (i - 1) * m + j;
extra[pos] = make_pair(i,t1 + (t1 + t2) * (j - 1));
}
}
signed main() {
scanf("%d%d%lf%lf%lf",&n,&m,&t1,&t2,&v);
t1 /= 60;
for (register int i = 1; i <= m; i++)
scanf("%d%d",&inv[0][i],&inv[1][i]);
for (register int i = 1; i <= n; i++)
scanf("%d%d",&mis[0][i],&mis[1][i]);
calcCost();
double lb = t1,ub = 1e6;
while (ub - lb > eps) {
double mid = (lb + ub) / 2;
if (judge(mid)) ub = mid;
else lb = mid;
}
printf("%.6f\n",ub);
return 0;
}