2021NUAA暑假集訓 Day4 部分題解
比賽連結:21.7.15-NUAA暑期集訓
比賽碼:NUAAACM20210715
目錄
A - 熱浪
單源最短路模板。
#include <cstring> #include <iostream> #include <queue> using namespace std; #define INF 0x3f3f3f3f #define io_speed_up ios::sync_with_stdio(false), cin.tie(0), cout.tie(0) int cnt, head[2510], dis[2510], n, m, s, t; bool vis[2510]; struct Edge { int v, w, next; } edge[12500]; void addEdge(int u, int v, int w) { edge[++cnt].v = v; edge[cnt].w = w; edge[cnt].next = head[u]; head[u] = cnt; } void spfa(int s) { queue<int> q; memset(vis, 0, sizeof(vis)); for (int i = 1; i <= n; ++i) { dis[i] = INF; } dis[s] = 0; vis[s] = true; q.push(s); while (!q.empty()) { int u = q.front(); vis[u] = false; q.pop(); for (int i = head[u]; i; i = edge[i].next) { int v = edge[i].v; if (dis[v] > dis[u] + edge[i].w) { dis[v] = dis[u] + edge[i].w; if (!vis[v]) { q.push(v); vis[v] = true; } } } } } int main() { io_speed_up; cin >> n >> m >> s >> t; for (int i = 1, u, v, w; i <= m; ++i) { cin >> u >> v >> w; addEdge(u, v, w); addEdge(v, u, w); } spfa(s); cout << dis[t]; return 0; }
B - Silver Cow Party
存兩張圖:
第一張圖正常存邊,用於求從\(x\)返回其他點時的最短路。
第二張圖存第一張圖的反向邊,由於從其他點到\(x\)是多源單終點,存反向邊則可以看作是從\(x\)到其他點,再求一遍最短路。
將各點在兩次最短路里的\(dis\)值相加,更新最大值。
#include <cstring> #include <iostream> #include <queue> using namespace std; #define INF 0x3f3f3f3f #define io_speed_up ios::sync_with_stdio(false), cin.tie(0), cout.tie(0) int cnt[2], head[2][1010], dis[2][1010], n, m, x; bool vis[1010]; struct Edge { int v, w, next; } edge[2][100010]; void addEdge(int k, int u, int v, int w) { edge[k][++cnt[k]].v = v; edge[k][cnt[k]].w = w; edge[k][cnt[k]].next = head[k][u]; head[k][u] = cnt[k]; } void spfa(int s, int k) { queue<int> q; memset(vis, 0, sizeof(vis)); for (int i = 1; i <= n; ++i) { dis[k][i] = INF; } dis[k][s] = 0; vis[s] = true; q.push(s); while (!q.empty()) { int u = q.front(); vis[u] = false; q.pop(); for (int i = head[k][u]; i; i = edge[k][i].next) { int v = edge[k][i].v; if (dis[k][v] > dis[k][u] + edge[k][i].w) { dis[k][v] = dis[k][u] + edge[k][i].w; if (!vis[v]) { q.push(v); vis[v] = true; } } } } } int main() { io_speed_up; cin >> n >> m >> x; for (int i = 1, u, v, w; i <= m; ++i) { cin >> u >> v >> w; addEdge(0, u, v, w); addEdge(1, v, u, w); } spfa(x, 0); spfa(x, 1); int ans = 0; for (int i = 1; i <= n; ++i) { ans = max(ans, dis[0][i] + dis[1][i]); } cout << ans << endl; return 0; }
C - Intervals
該題是一個差分約束系統。
設\(f[i]\)表示在\([0,i]\)區間內取了多少個數。
由輸入可以得到多個不等式\(f[b] - f[a - 1] \geq c\),則在建圖過程中點\(a-1\)有一條權值為\(c\)的有向邊到點\(b\)。
同時題目本身存在隱藏限制條件\(1\geq f[i] - f[i-1] \geq 0(0 \leq i \leq maxn)\),即\(\left\{\begin{matrix}f[i] - f[i-1] \geq 0 \\f[i-1] - f[i] \geq -1\end{matrix}\right.\),則在建圖過程中每個點\(i-1\)
由於區間左端最小可以為\(0\),所以需要將陣列下標加\(1\),防止出現下標為\(-1\)的情況。
建圖完成之後跑最長路即可。
#include <iostream>
#include <queue>
using namespace std;
#define io_speed_up ios::sync_with_stdio(false), cin.tie(0), cout.tie(0)
struct Edge {
int v, w, next;
} edge[5000010];
int n, a, b, c, cnt, head[50005], dis[50005], maxb;
bool vis[50005];
void addEdge(int u, int v, int w) {
edge[++cnt].v = v;
edge[cnt].w = w;
edge[cnt].next = head[u];
head[u] = cnt;
}
void spfa(int s) {
queue<int> q;
for (int i = 0; i <= maxb + 1; ++i) {
dis[i] = -1;
}
dis[s] = 0;
vis[s] = true;
q.push(s);
while (!q.empty()) {
int u = q.front();
q.pop();
vis[u] = false;
for (int i = head[u]; i; i = edge[i].next) {
int v = edge[i].v;
if (dis[v] < dis[u] + edge[i].w) {
dis[v] = dis[u] + edge[i].w;
if (!vis[v]) {
q.push(v);
vis[v] = true;
}
}
}
}
}
int main() {
io_speed_up;
cin >> n;
for (int i = 1; i <= n; ++i) {
cin >> a >> b >> c;
addEdge(a, b + 1, c);
maxb = maxb >= b ? maxb : b;
}
for (int i = 1; i <= maxb + 1; ++i) {
addEdge(i - 1, i, 0);
addEdge(i, i - 1, -1);
}
spfa(0);
cout << dis[maxb + 1] << endl;
return 0;
}
D - 飛行路線
建立多層圖,最多免費\(k\)次,所以有\(k+1\)層圖,第\(i\)層表示當前已用\(i\)次免費機會,加邊時在各層的都加上同一條邊,一次免費相當於在上下兩層的兩點之間建立一條權值為\(0\)的邊,起點在第\(0\)層,對整個多層圖跑一遍最短路,一個點的最短距離就是其在各層中的最短距離的最小值。
#include <cstdio>
#include <cstring>
#include <iostream>
#include <queue>
using namespace std;
#define io_speed_up ios::sync_with_stdio(false), cin.tie(0), cout.tie(0)
#define INF 0x3f3f3f3f
int head[110010], n, m, k, s, t, cnt;
long long dis[110010];
bool vis[110010];
struct Edge {
int v, next;
long long w;
} edge[11000010];
void addEdge(int u, int v, long long w) {
edge[++cnt].v = v;
edge[cnt].w = w;
edge[cnt].next = head[u];
head[u] = cnt;
}
void spfa(int s) {
queue<int> q;
memset(vis, 0, sizeof(vis));
for (int i = 0; i < k * n + n; ++i) {
dis[i] = INF;
}
dis[s] = 0;
vis[s] = true;
q.push(s);
while (!q.empty()) {
int u = q.front();
vis[u] = false;
q.pop();
for (int i = head[u]; i; i = edge[i].next) {
int v = edge[i].v;
if (dis[v] > dis[u] + edge[i].w) {
dis[v] = dis[u] + edge[i].w;
if (!vis[v]) {
q.push(v);
vis[v] = true;
}
}
}
}
}
int main() {
io_speed_up;
cin >> n >> m >> k >> s >> t;
for (int i = 1, u, v, w; i <= m; ++i) {
cin >> u >> v >> w;
for (int j = 0; j <= k; ++j) {
int tu = u + n * j, tv = v + n * j;
if (j != k) {
addEdge(tu, tv + n, 0);
addEdge(tv, tu + n, 0);
}
addEdge(tu, tv, w);
addEdge(tv, tu, w);
}
}
spfa(s);
long long ans = INF;
for (int i = 0; i <= k; ++i) {
ans = min(ans, dis[t + n * i]);
}
cout << ans;
return 0;
}
E - Watering Hole
在已有圖中新增一個點,表示地下天然水源,該點與其他各點之間建立一條權值為打井費用的邊,在某一點打井相當於選擇該點與地下水源之間的邊,然後跑最小生成樹即可。
#include <algorithm>
#include <cstdio>
#include <iostream>
using namespace std;
int n, fa[310], cnt;
struct Edge {
int u, v, w;
bool operator<(const Edge &obj) const { return w < obj.w; }
} edge[45310];
int find(int x) { return fa[x] == x ? x : fa[x] = find(fa[x]); }
int kruscal() {
int ans = 0;
for (int i = 0; i <= n; ++i) {
fa[i] = i;
}
int tot = 0;
for (int i = 1; i <= cnt; ++i) {
int u = edge[i].u, v = edge[i].v;
int fau = find(u), fav = find(v);
if (fau != fav) {
fa[fau] = fav;
tot++;
ans += edge[i].w;
if (tot == n) {
return ans;
}
}
}
}
int main() {
while (scanf("%d", &n) == 1) {
cnt = 0;
for (int i = 1; i <= n; ++i) {
edge[++cnt].u = 0;
edge[cnt].v = i;
scanf("%d", &edge[cnt].w);
}
for (int i = 1, w; i <= n; ++i) {
for (int j = 1; j <= n; ++j) {
scanf("%d", &w);
if (i < j) {
edge[++cnt].u = i;
edge[cnt].v = j;
edge[cnt].w = w;
}
}
}
sort(edge + 1, edge + cnt + 1);
printf("%d\n", kruscal());
}
return 0;
}
G - The Perfect Stall
二分圖匹配模板。
#include <cstring>
#include <iostream>
using namespace std;
#define io_speed_up ios::sync_with_stdio(false), cin.tie(0), cout.tie(0)
int n, m, link[210], ans;
bool vis[210], g[210][210];
bool hungry(int now) {
for (int i = 1; i <= n; i++) {
if (!vis[i] && g[now][i]) {
vis[i] = true;
if (!link[i] || hungry(link[i])) {
link[i] = now;
return true;
}
}
}
return false;
}
int main() {
io_speed_up;
while (cin >> n >> m) {
memset(g, 0, sizeof(g));
memset(link, 0, sizeof(link));
for (int i = 1, s, x; i <= n; ++i) {
cin >> s;
while (s--) {
cin >> x;
g[i][x] = true;
}
}
ans = 0;
for (int i = 1; i <= n; ++i) {
memset(vis, 0, sizeof(vis));
ans += hungry(i);
}
cout << ans << endl;
}
return 0;
}