P10936 导弹防御塔 题解

题目链接

题目大意

城堡有 m 个敌人、n 个能发射导弹的防御塔。导弹的速度固定,都为 v。导弹需要 T1 秒发射,T2 分钟冷却,还需要防御塔到敌人距离的 dis/v 的时间。给定防御塔和敌人的坐标,求需要多少分钟能够消灭所有敌人。

推导思路

如果短的时间能够消灭所有敌人,则长的也一定能。所以答案具有单调性,可以通过二分答案将求解转为判定。而该问题的判定,类似于一个导弹与敌人互相匹配的问题。对于这个问题,二分图的最大匹配就是一个不错的求解思路。

二分图最大匹配

细节实现

由于每个导弹可以多次匹配,所以转化为多次匹配的拆点做法。设左部为敌人,右部为导弹,遍历连边即可。连边的细节较多,在下面的代码中有注释体现。

代码

cpp 复制代码
// Problem: P10936 导弹防御塔
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P10936
// Memory Limit: 512 MB
// Time Limit: 1000 ms
// Date: 2024-12-20 18:43:22
// 
// Powered by CP Editor (https://cpeditor.org)

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
#define mst(x, y) memset(x, y, sizeof(x))
#define pii pair<int, int>
#define fi first
#define se second
#define mp(x, y) make_pair(x, y)

const int N = 55, M = 200005, inf = 0x3f3f3f3f; //记得空间开足够大,可以在不影响时间的情况下(memset)大概估一下
const double eps = 1e-8;

int n, m, idx;
int vis[M], mat[M], hd[M], ver[M], nxt[M];
double t1, t2, v;
struct pos{
	int x, y;
}ene[N], tow[N];

void add(int x, int y){
	nxt[++idx] = hd[x];
	ver[idx] = y;
	hd[x] = idx;
}
double cal(int i, int j){ //计算导弹飞行到敌人时间
	return sqrt((ene[i].x-tow[j].x)*(ene[i].x-tow[j].x)+(ene[i].y-tow[j].y)*(ene[i].y-tow[j].y))/v;
}
bool dfs(int x){ //二分图最大匹配增广路算法板子
	for(int i = hd[x];i;i = nxt[i]){
		int y = ver[i];
		if(vis[y]) continue;
		vis[y] = 1;
		if(!mat[y] || dfs(mat[y])){
			mat[y] = x;
			return true;
		}
	}
	return false;
}
bool check(double x){ //x为最大时间
	mst(mat, 0);mst(hd, 0);idx = 0; //最大匹配+链式前向星存图初始化
	int maxk = min(m, (int)floor((x+t2)/(t1+t2))); //maxk为最大时间内导弹最多的发生数(因为求的是最多次数,所以不考虑时间)
	for(int i = 1;i <= m;i++){ //遍历敌人
		for(int j = 1;j <= n;j++){ //遍历防御塔
			for(int k = 1;k <= maxk;k++){ //遍历导弹个数
				if(double(cal(i, j)+(k-1)*(t1+t2)+t1) > x) continue; //判断对于第i个敌人第j个防御塔的第k个导弹,是否能在最大时间限制内发射
				//这里需要详细注意一下,(j-1)*maxk+k即为第j个防御塔的第k个导弹编号,但导弹编号会和敌人编号重复,所以将导弹编号加上敌人个数防止重复
				add(i, m+(j-1)*maxk+k); 
				add(m+(j-1)*maxk+k, i); //连双向边
			}
		}
	}
	for(int i = 1;i <= m;i++){
		mst(vis, 0);
		if(!dfs(i)) return false; //如果有敌人无法匹配到导弹(即不能在最大时间内被杀死),则直接返回false
	}
	return return;
}
void init(){
	cin >> n >> m >> t1 >> t2 >> v;
	t1 /= 60;//t1单位为秒,要转化为分钟
	for(int i = 1;i <= m;i++) cin >> ene[i].x >> ene[i].y;
	for(int i = 1;i <= n;i++) cin >> tow[i].x >> tow[i].y;
}
void solve(){
	double l = 0, r = 100000, mid;
	while(r-l >= eps){//实数二分
		mid = (l+r)/2;
		if(check(mid)) r = mid;
		else l = mid;
	}
	printf("%.6lf", l);
}

int main(){
	init();
	solve();
	return 0; 
}