11月5日晚模拟赛题解
T1 赛艇表演
题意:\(n\)个点,\(m\)条边,每个点有权值,每条边也有权值(按读入$\times$2处理),对每个点,选定一个目标点(可以是它自己),使这个点到达目标点的距离加上目标点的权值的和最小。
\(1 <= n, m<=2e5\)
解法:
\(dijkstra\) + 超级源点
这个数据范围不允许我们跑\(floyd\)或者\(n\)次\(dijkstra\),但我们发现,对于每个点,只需求一个最小值,并且数据范围其实也告诉我们了只需要跑一次\(dijkstra\),那么我们该从那个点跑\(dijkstra\)呢?我们发现很难从图中找到这样的一个点,使得所有的点都和它有关。
我们再梳理一下思路,我们需要找到一个点,以TA为起点跑一遍单源最短路,并且我们要使得所有的点都和它有关。
我们发现这个点在图中无法找到,所以我们选择建立一个超级源点,让它想所有的点建一条长度为点的权值的边,然后从它开始跑一遍\(dijkstra\)。仔细思考一番,我们发现这个算法的正确性是对的,时间复杂度\(O(nlogn)\)
#include <cstdio>
#include <algorithm>
#include <queue>
#include <cstring>
using namespace std;
#define maxn 200100
#define pr pair<ll, int>
#define mp make_pair
#define ll long long
int fir[maxn], nxt[maxn * 3], vv[maxn * 3];
ll edg[maxn * 3];
int tot = 0, n, m;
void add(int u, int v, ll w)
{
nxt[++tot] = fir[u];
fir[u] = tot;
vv[tot] = v;
edg[tot] = w;
}
ll dis[maxn], vis[maxn], val[maxn];
priority_queue<pr, vector<pr>, greater<pr> >q;
ll dijkstra(int x)
{
for(int i = 1; i <= n + 1; i++) { dis[i] = 1e9; dis[i] *= 1e9; }
memset(vis, 0, sizeof(vis));
dis[x] = 0;
q.push(mp(dis[x], x));
while(!q.empty())
{
pr tmp = q.top(); q.pop();
int u = tmp.second;
if(vis[u] == 1) continue;
vis[u] = 1;
for(int i = fir[u]; i; i = nxt[i])
{
int v = vv[i];
if(dis[v] > dis[u] + edg[i])
{
dis[v] = dis[u] + edg[i];
q.push(mp(dis[v], v));
}
}
}
ll ans = 1e9; ans *= 1e9;
for(int i = 1; i <= n; i++) printf("%d ", dis[i]);
return ans;
}
int main()
{
scanf("%d%d", &n, &m);
for(int i = 1; i <= m; i++)
{
int u, v; ll w; scanf("%d%d%lld", &u, &v, &w);
add(u, v, w * 2); add(v, u, w * 2);
}
for(int i = 1; i <= n; i++) scanf("%lld", &val[i]);
for(int i = 1; i <= n; i++) add(n + 1, i, val[i]);
dijkstra(n + 1);
return 0;
}
T2 逮虾户
这是一道做法很简单的实数二分,但要注意\(l,r\)的取值范围,\(r\)最大可以取到\(1e6 +1e3\)
同时,要注意\(double\)的精度范围,\(double\)的有效范围是\(15\)\(到16\)位,\(long\,\,double\)的有效范围是\(18\)到\(19\)位,如果暴范围了,就很有可能\(l,r\)变成\(0\),然后\(T\)掉。
T3 战略威慑
这是一道很水的枚举删边,然后求树的直径的题,略过。