题解 P4277 【河城荷取的烟花】
一些前言
这道题对于我们机房有着里程碑一样的意义,因为它让我们最能抄题解最菜的那只当场退役。
那次是高一NOIP之后不久的一次模拟赛,考了三道题,这个是T3,考完之后大部分人前两题拿了大部分分,T3爆零,那兄弟前两题爆零,T3切了,后来教练发现是copy的Solution,他就退役了。
(本来想放个我们学校OJ的截图,但是他被我们教练从OJ里踢了,表格里没有他了。
解题思路
考虑把大的问题逐步分解
首先我们整体解题框架就是枚举所有可以点燃的点作为方案,对于每一个方案,算出所有绳子被燃尽的时间的最大值,求出这些最大值的最小值就是答案。
那么当我们确定了点燃点时,如何确定每根绳子被燃尽的时间?
为了方便存储,我们把一根绳子从中间折半分成两根存储。我们以拆完后所有的绳子端点为节点,以每根绳子燃烧时间为边权建图。显然,从
已知绳子两端点的点燃时间和燃烧速度,如何求出绳子燃尽时间?显然是小学相遇问题,大家都会推。
这样这个问题就被完美解决了。
程序实现
这道题的思路很巧,并且程序实现细节较多,大家可以看代码注释。
#include <iostream>
#include <cstdio>
#include <iomanip>
#include <cstring>
//防CE+防抄袭
#define x1 partychicken
#define x2 vcode
#define y1 ubospica
#define y2 greygoods
using namespace std;
const int MAXN = 2005;
const double EPS = 1e-6;
const double INF = 1e9;
int n, m;
int id[4005][4005];//如果坐标(i, j)是绳子端点,那么建的图中的节点为id[i][j]
int cant[MAXN];//绳子折半后中间点不能作为开始点,在这标一下
int to[MAXN], nxt[MAXN], head[MAXN], ecnt;
double val[MAXN], dis[MAXN];
int lid[MAXN], rid[MAXN], tot;//每根绳子两端点对应图中的节点编号
double v[MAXN];//每根绳子燃烧速度
double ans;
void Add(int u, int v, double w) {
to[++ecnt] = v; val[ecnt] = w; nxt[ecnt] = head[u]; head[u] = ecnt;
to[++ecnt] = u; val[ecnt] = w; nxt[ecnt] = head[v]; head[v] = ecnt;
// cerr << u << ' ' << v << ' ' << w << '\n';
}
void SetStick(int x1, int y1, int x2, int y2, double len) {
x1 <<= 1; x1 += 2001;//为了便于计算,把坐标调整调正
y1 <<= 1; y1 += 2001;
x2 <<= 1; x2 += 2001;
y2 <<= 1; y2 += 2001;
int x3 = (x1 + x2 >> 1), y3 = (y1 + y2 >> 1);//中间点
if (!id[x1][y1]) id[x1][y1] = ++n;//在图中建出节点
if (!id[x2][y2]) id[x2][y2] = ++n;
if (!id[x3][y3]) id[x3][y3] = ++n;
cant[id[x3][y3]] = 1;//中间点不能点燃
Add(id[x1][y1], id[x3][y3], len / 2.0);
Add(id[x3][y3], id[x2][y2], len / 2.0);
lid[++tot] = id[x1][y1]; rid[tot] = id[x3][y3]; v[tot] = 1.0 / len;//记录每条边的信息
lid[++tot] = id[x3][y3]; rid[tot] = id[x2][y2]; v[tot] = 1.0 / len;
}
int q[MAXN], ql, qr;
int vis[MAXN];
void SPFA(int s) {//单源最短路
for (int i = 1; i <= n; i++) dis[i] = INF; dis[s] = 0;
ql = qr = 1; q[1] = s;
memset(vis, 0, sizeof(vis)); vis[s] = 1;
while (ql <= qr) {
int u = q[ql++ % n]; vis[u] = 0;
for (int i = head[u]; i; i = nxt[i]) {
int v = to[i];
if (dis[v] - dis[u] > val[i] + EPS) {
dis[v] = dis[u] + val[i];
if (!vis[v]) {
vis[v] = 1;
q[++qr % n] = v;
}
}
}
}
// cerr << '*';
// for (int i = 1; i <= n; i++) cerr << dis[i] << ' ';
// cerr << '\n';
}
double EndTime(int sid) {//相遇问题,计算绳子被燃尽的时间
double t1 = dis[lid[sid]], t2 = dis[rid[sid]], V = v[sid];
if (t1 - t2 > EPS) swap(t1, t2);
if (t2 - t1 > 0.5 / V) return t1 + 0.5 / V;
double res1 = t2;
double res2 = (0.5 - V * (t2 - t1)) / (V * 2.0);
return res1 + res2;
}
void Cal(int s) {//计算从节点s点燃,所有绳子被燃尽的时间
SPFA(s);
double res = 0;
for (int i = 1; i <= tot; i++) res = max(res, EndTime(i));
ans = min(ans, res);//更新总答案
}
int main() {
cin >> m;
for (int i = 1; i <= m; i++) {
int x1, y1, x2, y2; double t;
cin >> x1 >> y1 >> x2 >> y2 >> t;
SetStick(x1, y1, x2, y2, t);
}
ans = INF;
for (int i = 1; i <= n; i++) {//枚举点燃点
if (cant[i]) continue;
Cal(i);
}
cout << fixed << setprecision(4) << ans << '\n';
return 0;
}