2020 EC Final D. City Brain
传送门
前几天我们队把ec final作为训练赛,没想到打的还挺不错,如果最后把这题做出来了,说不定还能拿au呢。
不过这一场中间的真空期有点长,导致想出D的正解的时间有点晚了。
首先对于两条路径,一定是中间有重合的一段(或者没有),而不是有两段,否则对于两个人中间分开的那一段路径一定可以一起选择一条更短的路走,从而将两段重合路径合并成了一段。
想到这一点主算法就出来了,结合数据范围,可以\(O(n^2)\)枚举重合的路径的两个端点,那么重合的路径长度以及不重合的路径的长度就可以确定了,不妨记为\(a\)和\(b\),那么问题就转化成,如何分配\(k_1,k_2(k_1+k_2=K)\),使提高限速后的路径和最短。
首先合乎常理的是,限速一定要均匀分配,以不重合路径\(b\)为例,可如果以提高\(k_2\)次限速,那么最优解就是
\[\sum\limits_{i=1}^b \frac1{\lfloor \frac{k_2}{n} \rfloor +1} + \sum\limits_{i=1}^{k_2 \mod b} \frac1{\lfloor \frac{k_2}{b} \rfloor +2}
\]
这个可以\(O(1)\)计算,对于重合路径方法相同,最后乘以2即可。
又观察到这是一个单峰函数,而两个单峰函数的和也是单峰函数,因此我们可以三分,在\(O(\log n)\)时间内求出在给定\(a,b\)的前提下的最优分配方案。
最后是对时间复杂度的优化。上述算法时间复杂度是\(O(n^2 \log n)\),会超时。优化在于我们只关注路径的长度,和结点没有关系,而重合路径的长度最多只有\(n\)种,因此我们可以先预处理对于长度为\(i\)的重合路径,其长度最短的不重合路径的长度,再进行三分。这样时间复杂度就是\(O(n^2 + n \log n)\).
#include<bits/stdc++.h>
using namespace std;
#define enter puts("")
#define space putchar(' ')
#define Mem(a, x) memset(a, x, sizeof(a))
#define In inline
#define forE(i, x, y) for(int i = head[x], y; ~i && (y = e[i].to); i = e[i].nxt)
typedef long long ll;
typedef double db;
const int INF = 0x3f3f3f3f;
const db eps = 1e-8;
In ll read() {ll x; scanf("%lld", &x); return x;}
In void write(ll x) {printf("%lld", x);}
const int maxn = 5145;
int n, m, K, s[2], t[2];
vector<int> ed[maxn];
int dis[maxn][maxn], path[maxn];
void bfs(int *dis, int s)
{
fill_n(dis, n + 15, INF);
queue<int> q;
q.push(s); dis[s] = 0;
while(q.size())
{
int u = q.front(); q.pop();
for(auto v : ed[u])
{
if(dis[v] == INF)
{
dis[v] = dis[u] + 1; q.push(v);
}
}
}
}
db calc(int t1, int t2, int s1, int s2)
{
db ans = 0;
if(t1)
{
int t11 = s1 % t1;
if(t11) ans += (1.0 * t11) / (s1 / t1 + 2);
t11 = t1 - t11;
if(t11) ans += (1.0 * t11) / (s1 / t1 + 1);
}
if(!t2) return ans;
int t22 = s2 % t2;
if(t22) ans += (2.0 * t22) / (s2 / t2 + 2);
t22 = t2 - t22;
if(t22) ans += (2.0 * t22) / (s2 / t2 + 1);
return ans;
}
db solve(int t1, int t2)
{
int L = 0, R = K;
db ret = 1e20;
int lm, rm;
db lret, rret;
while(L < R)
{
lm = (L + R) >> 1, rm = lm + 1;
lret = calc(t1, t2, lm, K - lm), rret = calc(t1, t2, rm, K - rm);
if(lret < rret)
{
R = rm - 1;
ret = min(ret, lret);
}
else
{
L = lm + 1;
ret = min(ret, rret);
}
}
return ret;
}
int main()
{
n = read(), m = read(), K = read();
for(int i = 1; i <= m; ++i)
{
int u = read(), v = read();
ed[u].push_back(v), ed[v].push_back(u);
}
s[0] = read(), t[0] = read(), s[1] = read(), t[1] = read();
for(int i = 1; i <= n; ++i) bfs(dis[i], i);
fill_n(path, n + 1, INF);
for(int u = 1; u <= n; ++u)
for(int v = 1; v <= n; ++v) //要考虑不连通的情况
{
if(dis[s[0]][u] == INF || dis[s[1]][u] == INF || dis[v][t[0]] == INF || dis[v][t[1]] == INF || dis[u][v] == INF) continue;
int tp = dis[u][v];
path[tp] = min(path[tp], dis[s[0]][u] + dis[s[1]][u] + dis[v][t[0]] + dis[v][t[1]]);
if(dis[t[1]][u] == INF || dis[v][s[1]] == INF) continue;
path[tp] = min(path[tp], dis[s[0]][u] + dis[t[1]][u] + dis[v][t[0]] + dis[v][s[1]]);
}
db ans = calc(dis[s[0]][t[0]] + dis[s[1]][t[1]], 0, K, 0);
for(int i = 0; i <= n; ++i)
if(path[i] != INF) ans = min(ans, solve(path[i], i));
printf("%.10lf", ans);
}