9/2模拟赛题解
T1. SYOJ #1066 / Luogu P1144
最短路 / 最短路计数
【大体思路】
BFS
【详解】
思路其实非常好想,边权都为 1,简简单单一个 BFS 就可以。洛谷这道题范围蛮逆天的,需要用 vector 存邻接表。
因为第一次 BFS 到的点一定是从起点经过了最小的距离到达的,所以可以直接记下目前走的步数,以后要是还用这些步再走到这个点就可以累加到这个点的方案数了。
具体累加方法就是:从不同的 \(x\) 一步走到 \(y\),且用了最小步数,就把每个走到 \(x\) 的方案数加进 \(y\) 的方案数中。
【代码】
看看word码
#include<iostream>
#include<algorithm>
#include<cstring>
#include<vector>
#include<cstdio>
#include<queue>
using namespace std;
typedef long long ll;
const int N = 2e5 + 10;
const int mod = 1e5 + 3;
int n, m, ans[N];
int vis[N];
vector<int>edge[N];
queue<int>q;
void bfs() {
vis[1] = 1;
ans[1] = 1;
q.push(1);
while (q.size()) {
int x = q.front();
q.pop();
for (int i = 0; i < edge[x].size(); i++) {
int y = edge[x][i];
if (!vis[y]) {
vis[y] = vis[x] + 1;
q.push(y);
}
if (vis[y] == vis[x] + 1) {
ans[y] = (ans[y] + ans[x]) % mod;
}
}
}
}
int x, y;
int main() {
scanf("%d%d", &n, &m);
while (m--) {
scanf("%d%d", &x, &y);
edge[x].push_back(y);
edge[y].push_back(x);
}
bfs();
for (int i = 1; i <= n; i++) {
printf("%d\n", ans[i] % mod);
}
return 0;
}
T2. SYOJ #1067 / Luogu P1948 / [USACO08JAN]
灾后重建 / Telephone Lines S
【大体思路】
DP + 最短路(SPFA)
【详解】
其实就是多加一维使用免费线的数量,转移的时候要转移两次状态,一次是在还有使用次数的时候使用免费线,花费为 0 直接跳到另一个点,另一次是不使用免费线,花费一些代价跳到新的点上。
具体实现方法见代码。
【代码】
看看word码
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<queue>
using namespace std;
typedef long long ll;
#define mp(a,b) make_pair(a,b)
const int N = 1e3 + 5;
const int M = 1e4 + 5;
const int inf = 0x3f3f3f3f;
int n, m, k, ans;
int vis[N][M], dist[N][M];
int head[N], edge[M * 2], nxt[M * 2], w[M * 2], tot;
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 spfa() {
memset(dist, 0x3f, sizeof(dist));
memset(vis, 0, sizeof(vis));
dist[1][0] = 0;
vis[1][0] = 1;
q.push(mp(1, 0));
while (q.size()) {
int x = q.front().first;
int p = q.front().second;
q.pop();
vis[x][p] = 0;
for (int i = head[x]; i; i = nxt[i]) {
int y = edge[i];
int z = w[i];
if (dist[y][p] > max(dist[x][p], z)) {
dist[y][p] = max(dist[x][p], z);
if (!vis[y][p]) {
vis[y][p] = 1;
q.push(mp(y, p));
}
}
if (p < k && dist[y][p + 1] > dist[x][p]) {
dist[y][p + 1] = dist[x][p];
if (!vis[y][p + 1]) {
vis[y][p + 1] = 1;
q.push(mp(y, p + 1));
}
}
}
}
}
int main() {
scanf("%d%d%d", &n, &m, &k);
while (m--) {
int x, y, z;
scanf("%d%d%d", &x, &y, &z);
add(x, y, z);
add(y, x, z);
}
spfa();
ans = inf;
for (int i = 0; i <= k; i++) {
ans = min(ans, dist[n][i]);
}
if (ans == inf) {
printf("-1");
return 0;
}
printf("%d", ans);
return 0;
}
T3. SYOJ #1068 / BZOJ 4152
卡尔距离
【大体思路】
最短路(Dijkstra)
【详解】
这道题直接给所有点两两建边会大爆炸,所以我们要好好考虑下题目的性质。
其实不难发现,如果两点之间联通,它们之间的距离一定是路径上相邻两点间最小的横坐标差或者是纵坐标差。这样也就把建边爆炸的问题解决了,只需要对横坐标个纵坐标进行一个排序,在相邻两点之间建边就可以了。
【代码】
看看word码
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<queue>
using namespace std;
typedef long long ll;
const int N = 2e5 + 5;
const int M = 4e5 + 5;
const ll inf = 0x3f3f3f3f3f3f3f3f;
int n, m;
ll dist[N], w[M * 2];
bool vis[N];
int head[N], edge[M * 2], nxt[M * 2], tot;
struct node {
int id, x, y;
} a[N];
void add(int x, int y, ll z) {
edge[++tot] = y;
nxt[tot] = head[x];
w[tot] = z;
head[x] = tot;
}
bool cmp1(node i, node j) {
return i.x < j.x;
}
bool cmp2(node i, node j) {
return i.y < j.y;
}
priority_queue<pair<int, int> >q;
void dijkstra() {
for (int i = 1; i <= n; i++) {
dist[i] = inf;
}
dist[1] = 0;
q.push(make_pair(0, 1));
while (q.size()) {
int x = q.top().second;
q.pop();
vis[x] = 1;
for (int i = head[x]; i; i = nxt[i]) {
int y = edge[i];
ll z = w[i];
if (dist[y] > dist[x] + z && !vis[y]) {
dist[y] = dist[x] + z;
q.push(make_pair(-dist[y], y));
}
}
}
}
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
scanf("%d%d", &a[i].x, &a[i].y);
a[i].id = i;
}
sort(a + 1, a + n + 1, cmp1);
for (int i = 2; i <= n; i++) {
add(a[i - 1].id, a[i].id, a[i].x - a[i - 1].x);
add(a[i].id, a[i - 1].id, a[i].x - a[i - 1].x);
}
sort(a + 1, a + n + 1, cmp2);
for (int i = 2; i <= n; i++) {
add(a[i - 1].id, a[i].id, a[i].y - a[i - 1].y);
add(a[i].id, a[i - 1].id, a[i].y - a[i - 1].y);
}
dijkstra();
printf("%lld", dist[n]);
return 0;
}
T4. SYOJ #1077 / BZOJ 2165
奶酪 - 学好语文很重要 / 大楼
【大体思路】
倍增 + 最短路(Floyd)
【详解】
真的不很会倍增,且题意很难懂。事实就是比赛时没读懂题把这道题直接扔了。
不过似乎懂了就很板了,一些套矩阵快速幂啥的,具体看看码吧还是。
【代码】
看看word码
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
using namespace std;
typedef long long ll;
const int N = 110;
int T, n;
ll m, ans;
struct Mat {
ll a[N][N];
bool check() {
for (int i = 1; i <= n; i++) {
if (a[1][i] >= m) {
return 1;
}
}
return 0;
}
} f[N], tmp1, tmp2;
Mat operator *(const Mat &x, const Mat &y) {
Mat z;
memset(z.a, -1, sizeof(z.a));
for (int i = 1; i <= n; i++) {
for (int k = 1; k <= n; k++) {
if (~x.a[i][k]) {
for (int j = 1; j <= n; j++) {
if (~y.a[k][j]) {
z.a[i][j] = max(z.a[i][j], x.a[i][k] + y.a[k][j]);
}
}
}
}
}
return z;
}
int main() {
scanf("%d", &T);
while (T--) {
scanf("%d%lld", &n, &m);
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
ll x;
scanf("%lld", &x);
if (!x) {
f[0].a[i][j] = -1;
} else {
f[0].a[i][j] = x;
}
}
}
int t = 1;
while (t++) {
f[t + 1] = f[t] * f[t];
if (f[t + 1].check()) {
break;
}
}
tmp1 = f[0];
ans = 1;
for (int i = t; i >= 0; i--) {
tmp2 = tmp1 * f[i];
if (!tmp2.check()) {
for (int j = 1; j <= n; j++) {
for (int k = 1; k <= n; k++) {
tmp1.a[j][k] = tmp2.a[j][k];
}
}
ans += 1LL << i;
}
}
printf("%lld\n", ans + 1);
}
return 0;
}
T5. SYOJ #1080 / Luogu P4308
旅行到永久 / 幸福路径
【大体思路】
倍增 + 最短路(Floyd)
【详解】
也是一个倍增的思想,当走了非常多条边以后精度会变得很小,这时把它近似地看做答案就可以了。
于是在 Floyd 的基础上再添加一维,\(f[t][i][j]\) 表示走了 \(2^t\) 条边后从 \(i\) 到 \(j\) 的最短距离。枚一下 \(t\) 就好了。虽然但是 35 次就可以过,好像蛮水的。
转移方程: $$f[t][i][j]=max(f[t−1][i][k]+f[t−1][k][j]*p ^ {2^{t−1}})$$
【代码】
看看word码
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
using namespace std;
typedef long long ll;
const int T = 40;
const int N = 105;
const double eps = 1e-10;
const int inf = 0x3f3f3f3f;
int n, m, s;
double p, a[N], f[T][N][N], ans;
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++) {
scanf("%lf", &a[i]);
}
scanf("%d%lf", &s, &p);
for (int k = 0; k <= 35; k++) {
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
if (i == j) {
continue;
}
f[k][i][j] = -inf;
}
}
}
while (m--) {
int x, y;
scanf("%d%d", &x, &y);
f[0][x][y] = a[y] * p;
}
for (int t = 1; t <= 35; t++) {
if (p < eps) {
break;
}
for (int k = 1; k <= n; k++) {
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
if (f[t][i][j] < f[t - 1][i][k] + f[t - 1][k][j] * p) {
f[t][i][j] = f[t - 1][i][k] + f[t - 1][k][j] * p;
}
}
}
}
p *= p;
}
for (int t = 1; t <= 35; t++) {
for (int j = 1; j <= n; j++) {
ans = max(ans, f[t][s][j]);
}
}
printf("%.1lf", ans + a[s]);
return 0;
}
T6. SYOJ #1081 / Luogu P3008 / [USACO11JAN]
货物运输 - 航线与隧道 / Roads and Planes G
【大体思路】
最短路(Spfa)
【详解】
因为有负权边,所以选择 Spfa,好像有点小卡时,不慌,加点奇妙的小优化就好了。
类似于优先队列优化 Dij,我们这里使用双端队列。把更优的点人为加入到队头,不优就扔到队尾,这样也就避免了一些重复更新的情况。
不过似乎这个方法也并不能真正变快,只是这个题数据比较水可以直接过这样。更好的解法似乎是缩点 + Dij + 拓扑排序。
【代码】
看看word码
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<queue>
using namespace std;
typedef long long ll;
const int N = 2e5 + 5;
const int M = 2e5 + 5;
const int inf = 0x3f3f3f3f;
int n, m, k, s, ans;
int vis[N], dist[N];
int head[N], edge[M * 2], nxt[M * 2], w[M * 2], tot;
deque<int>q;
void add(int x, int y, int z) {
edge[++tot] = y;
nxt[tot] = head[x];
w[tot] = z;
head[x] = tot;
}
void spfa() {
memset(dist, 0x3f, sizeof(dist));
memset(vis, 0, sizeof(vis));
dist[s] = 0;
vis[s] = 1;
q.push_back(s);
while (q.size()) {
int x = q.front();
q.pop_front();
vis[x] = 0;
for (int i = head[x]; i; i = nxt[i]) {
int y = edge[i];
int z = w[i];
if (dist[y] > dist[x] + z) {
dist[y] = dist[x] + z;
if (!vis[y]) {
vis[y] = 1;
if (q.size() && dist[y] < dist[q.front()]) {
q.push_front(y);
} else {
q.push_back(y);
}
}
}
}
}
}
int main() {
scanf("%d%d%d%d", &n, &m, &k, &s);
while (m--) {
int x, y, z;
scanf("%d%d%d", &x, &y, &z);
add(x, y, z);
add(y, x, z);
}
while (k--) {
int x, y, z;
scanf("%d%d%d", &x, &y, &z);
add(x, y, z);
}
spfa();
for (int i = 1; i <= n; i++) {
if (dist[i] == inf) {
printf("Impossible \n");
continue;
}
printf("%d\n", dist[i]);
}
return 0;
}