9/9模拟赛题解
T1. SYOJ #1007 / Luogu P3199
环形跑
【大体思路】
二分 + 0/1 分数规划 + Spfa(DFS优化判负环)
【详解】
有一说一,其实不知道 0/1 分数规划是啥也能做。正向考虑找边权平均值最小的环非常困难,所以我们不妨从结果入手反推找有没有符合要求的环。这样二分的思路就明确了,然后就突然想到之前做过一道关于平均值的二分题,这里搬个当时写的题解看看。
我们把环上的每个元素都减去二分出的平均值,在这个基础上跑最短路找环。因为要找的是平均值最小的环,正好与上面这道题相反,如果找到了负环就说明有比二分出的平均值符合要求且存在更优的答案,可以再接着缩小二分范围。
这里的 Spfa 使用递归写法,求负环比较方便。
【代码】
看看word码
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
using namespace std;
const int N = 5e3 + 5;
const int M = 1e5 + 5;
const double inf = 1e9;
const double eps = 1e-10;
int n, m;
int head[N], edge[M], nxt[M], w[M], tot;
double l = -inf, r = inf, vis[N], f, dist[N];
void add(int x, int y, double z) {
edge[++tot] = y;
nxt[tot] = head[x];
w[tot] = z;
head[x] = tot;
}
void check(int x, double d) {
vis[x] = 1;
for (int i = head[x]; i; i = nxt[i]) {
int y = edge[i];
double z = w[i];
if (dist[y] > dist[x] + z - d) {
if (vis[y] || f) {
f = 1;
break;
}
dist[y] = dist[x] + z - d;
check(y, d);
}
}
vis[x] = 0;
return ;
}
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= m; i++) {
int a, b;
double c;
scanf("%d%d%lf", &a, &b, &c);
add(a, b, c);
}
while (r - l > eps) {
memset(dist, 0, sizeof(dist));
memset(vis, 0, sizeof(vis));
f = 0;
double mid = (l + r) / 2;
for (int i = 1; i <= n; i++) {
check(i, mid);
if (f) {
break;
}
}
if (f) {
r = mid;
} else {
l = mid;
}
}
if (r == inf) {
printf("No solution");
} else {
printf("%.8lf", l);
}
return 0;
}
T2. SYOJ #1019 / Luogu P5764
奶牛串门 / 新年好
【大体思路】
枚举 + 最短路(Dijkstra)
【详解】
我们不知道以一个怎样的顺序可以使走出的距离最短,但是知道一共只有五个点,把这所有的可能性都枚举一遍也只有 \(5!\) 种情况,还是很能接受的。
事实上,在看到这道题的时候第一想法是分层图,把点建五层,从最下面一层开始,每走到一个新的目标点就升一层,最终在第五层统计答案。在写的时候遇到了一个困难,就是可能会在一个环上一直重复走爬层,虽然是走到了足够多的目标点,但是是重复计数的。给目标点原来的编号打标记是不行的,只能硬判断是不是走到了没走过的目标点,以及还需要考虑目标点是可以重复到达的,但是不会计数。略微有亿点点麻烦。感觉这个做法理论上是可以的,但是不知道哪里写挂了,死活调不对,于是遗憾离场。
【代码】
看看word码
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<queue>
using namespace std;
const int inf = 0x3fffffff;
const int N = 5e4 + 5;
const int M = 1e6 + 10;
int n, m, ans = inf, dist[6][N], t[6];
int head[N], edge[M], nxt[M], w[M], tot;
int b[6] = {1, 2, 3, 4, 5};
bool vis[N];
priority_queue<pair<int, int> > q;
void add(int x, int y, int z) {
edge[++tot] = y;
nxt[tot] = head[x];
w[tot] = z;
head[x] = tot;
}
void dij(int k) {
memset(vis, 0, sizeof(vis));
dist[k][t[k]] = 0;
q.push(make_pair(0, t[k]));
while (!q.empty( )) {
int x = q.top().second;
q.pop();
if (vis[x]) {
continue;
}
vis[x] = 1;
for (int i = head[x]; i; i = nxt[i]) {
int y = edge[i], z = w[i];
if (vis[y]) {
continue;
}
dist[k][y] = min(dist[k][y], dist[k][x] + z);
q.push(make_pair(-dist[k][y], y));
}
}
}
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= 5; i++) {
scanf("%d", &t[i]);
}
t[0] = 1;
for (int i = 1; i <= m; i++) {
int x, y, z;
scanf("%d%d%d", &x, &y, &z);
add(x, y, z);
add(y, x, z);
}
memset(dist, 0x3f, sizeof(dist));
for (int i = 0; i <= 5; i++) {
dij(i);
}
ans = inf;
do {
int res = dist[0][t[b[0]]];
for (int j = 0; j <= 3; j++) {
res += dist[b[j]][t[b[j + 1]]];
}
ans = min(ans, res);
} while (next_permutation(b, b + 5));
printf("%d", ans);
return 0;
}
**T3. SYOJ #1020 / Luogu P1938 / [USACO09NOV] **
阿龙打工 / Job Hunt S
【大体思路】
Spfa判负环
【详解】
比赛开始30分钟静坐灵光乍现产物。
是看到可以无限赚钱的特判联想到了负环的性质,噶的一下就想建个负权边直接跑 Spfa,怒打码一测就发现好像还真有点意思。更有意思的是,当时根本没看见数据范围小小的很可爱适合 Floyd,然后在码里敲了下面这段:
//啊?咋没记得第一次看的时候数据范围可以跑 Floyd ,所以用了 Spfa
//等我写个对拍看看
实际上 Floyd 根本没打。
Spfa 跑的还是略快一点的。
【代码】
看看word码
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<queue>
using namespace std;
typedef long long ll;
const int N = 5e3 + 5;
const int M = 1e5 + 5;
int n, m, l, k, s;
int head[N], edge[M * 2], nxt[M * 2], tot, cnt[N];
bool vis[N],flag;
ll w[M * 2], dist[N], ans;
void add(int x, int y, ll z) {
edge[++tot] = y;
nxt[tot] = head[x];
w[tot] = z;
head[x] = tot;
}
priority_queue<pair<int, int> >q;
void spfa() {
memset(dist, 0x3f, sizeof(dist));
q.push(make_pair(0, s));
dist[s] = -l;
vis[s] = 1;
while (q.size()) {
int x = q.top().second;
q.pop();
vis[x] = 0;
for (int i = head[x]; i; i = nxt[i]) {
int y = edge[i], z = w[i];
if (dist[y] > dist[x] + z) {
dist[y] = dist[x] + z;
if (!vis[y]) {
if (++cnt[y] >= n) {
printf("-1");
flag=1;
exit(0);
}
vis[y] = 1;
q.push(make_pair(-dist[y], y));
}
}
}
}
}
int main() {
scanf("%d%d%d%d%d", &l, &m, &n, &k, &s);
while (m--) {
int x, y;
scanf("%d%d", &x, &y);
add(x, y, -l);
}
while (k--) {
int x, y, z;
scanf("%d%d%d", &x, &y, &z);
add(x, y, z - l);
}
spfa();
for (int i = 1; i <= n; i++) {
ans = min(ans, dist[i]);
}
printf("%lld", -ans);
return 0;
}
T4. SYOJ #1030 / Luogu P1266
奶牛开车 / 速度限制
【大体思路】
最短路(Dijkstra)
【详解】
这道题特别的就是要用两个量来计算边权,多加一维就能解决。然后就是输出路径,用个 \(pre\) 数组就可以实现类似链表的功能,再用个递归向上查倒着输出就好了。
思路真的不难,细节也是真的很多,调了快一节课。需要仔细处理 \(v\) 为 \(0\) 时的计算方式。
【代码】
看看word码
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<queue>
using namespace std;
typedef long long ll;
const int inf = 1e9;
const int N = 205;
const int T = 1005;
const int M = 1e5 + 10;
int n, m, a, b, c, maxx;
int head[N], edge[M * 2], nxt[M * 2], w[M * 2], V[M * 2], tot;
double dist[T][T];
bool vis[T][T];
pair<double, int>pre[T][T];
void add(int x, int y, int z, int v) {
edge[++tot] = y;
nxt[tot] = head[x];
w[tot] = z;
V[tot] = v;
head[x] = tot;
}
priority_queue<pair<double, pair<int, int> > >q;
void dijkstra() {
for (int i = 1; i <= n; i++) {
for (int j = 0; j <= 500; j++) {
dist[i][j] = 1e9;
}
}
dist[a][c] = 0;
q.push(make_pair(0, make_pair(a, c)));
while (q.size()) {
int x = q.top().second.first;
int lv = q.top().second.second;
q.pop();
if (vis[x][lv]) {
continue;
}
vis[x][lv] = 1;
for (int i = head[x]; i; i = nxt[i]) {
int y = edge[i], z = w[i], v = V[i];
if (vis[y][v]) {
continue;
}
if (!v) {
v = lv;
}
if (dist[y][v] > dist[x][lv] + 1.0 * z / v) {
dist[y][v] = dist[x][lv] + 1.0 * z / v;
pre[y][v].first = x;
pre[y][v].second = lv;
q.push(make_pair(-dist[y][v], make_pair(y, v)));
}
}
}
}
void print(int x, int v) {
if (x == a) {
printf("%d ", x);
return;
}
print(pre[x][v].first, pre[x][v].second);
printf("%d ", x);
}
int main() {
scanf("%d%d%d%d%d", &n, &m, &a, &b, &c);
while (m--) {
int x, y, z, v;
scanf("%d%d%d%d", &x, &y, &z, &v);
add(x, y, z, v);
}
dijkstra();
dist[b][maxx] = inf;
for (int i = 0; i <= 500; i++) {
if (dist[b][i] <= dist[b][maxx]) {
maxx = i;
}
}
print(b, maxx);
return 0;
}