做题记录 // 230325
我欲乘风归去,又恐天上下雨。高处不撑伞。
A. 灾后重建
http://222.180.160.110:1024/contest/3459/problem/1
\(n\le 200\),故考虑 Floyd。观察 Floyd 的实现,其最外层循环为枚举中转节点并更新,不难发现中转节点会随着时间的推移而增加。结合询问中 \(t\) 单调不下降,考虑在询问前处理完以新的中转节点中转的情况。
namespace XSC062 {
using namespace fastIO;
const int maxn = 205;
const int maxm = 5e4 + 5;
const int inf = 0x3f3f3f3f;
int p[maxn];
int g[maxn][maxn];
int n, m, x, y, t, la;
std::vector<int> a[maxm];
inline int min(int x, int y) {
return x < y ? x : y;
}
int main() {
read(n), read(m);
memset(g, 0x3f, sizeof (g));
for (int i = 1; i <= n; ++i) {
read(p[i]);
a[p[i]].push_back(i);
}
while (m--) {
read(x), read(y);
++x, ++y;
read(g[x][y]), g[y][x] = g[x][y];
}
read(m);
la = -1;
while (m--) {
read(x), read(y), read(t);
++x, ++y;
for (int u = la + 1; u <= t; ++u) {
for (auto k : a[u]) {
for (int i = 1; i <= n; ++i) {
if (i == k)
continue;
for (int j = 1; j <= n; ++j) {
if (j != k && j != i) {
g[i][j] = min(g[i][j],
g[i][k] + g[k][j]);
}
}
}
}
}
if (p[x] > t || p[y] > t || g[x][y] == inf)
puts("-1");
else print(g[x][y], '\n');
la = t;
}
return 0;
}
} // namespace XSC062
B. 最短路
http://222.180.160.110:1024/contest/3459/problem/2
非常直白的标题让我在点进去之前认为这是一个板子。我错了。
如果我们真的按照题目所说连边,那么只需要整个数列的值全部相同,就会存在 \(n^2\) 条边,不管是时间还是空间都会起飞。
我们发现两个点之间,至少需要一个公有的二进制位才能相连。那假如,我们把所有二进制位建虚点,并将点与其拥有的二进制位相连呢?
因为点与点之间没有直接连边,二进制位与二进制位之间也没有直接连边,它会变成一个二分图,其边数最多为 \(31\times n\)。
如何处理边权?考虑两个数,他们途径公有二进制位连边,边权应为两个点的权值之和。那这就很明显了,我们只需将一个点和其拥有的二进制位间连一条权值为该点数值的边即可。
namespace XSC062 {
using namespace fastIO;
const int inf = 1e18;
const int maxn = 1e5 + 5;
#define mkp std::make_pair
using pii = std::pair<int, int>;
struct _ {
int v, w;
_() {}
_(int v1, int w1) {
v = v1, w = w1;
}
};
int n;
bool vis[maxn];
int a[maxn], dis[maxn];
std::vector<_> g[maxn];
std::priority_queue<pii> q;
inline void add(int x, int y, int w) {
g[x].push_back(_(y, w));
return;
}
inline void Dijk(int s) {
for (int i = 2; i <= n + 32; ++i)
dis[i] = inf;
q.push(mkp(0, s));
while (!q.empty()) {
int f = q.top().second;
q.pop();
if (vis[f])
continue;
vis[f] = 1;
for (auto i : g[f]) {
if (dis[i.v] > dis[f] + i.w) {
dis[i.v] = dis[f] + i.w;
q.push(mkp(-dis[i.v], i.v));
}
}
}
return;
}
int main() {
read(n);
for (int i = 1; i <= n; ++i) {
read(a[i]);
for (int j = 0; j <= 31; ++j) {
if (a[i] & (1ll << j)) {
add(i, j + n + 1, a[i]);
add(j + n + 1, i, a[i]);
}
}
}
Dijk(1);
for (int i = 1; i <= n; ++i) {
if (dis[i] == inf)
print(-1, ' ');
else print(dis[i], ' ');
}
return 0;
}
} // namespace XSC062
C. Alias
http://222.180.160.110:1024/contest/3459/problem/3
这,这不就纯 Dij?
namespace XSC062 {
const int inf = 1e18;
const int maxn = 1e5 + 5;
#define mkp std::make_pair
using str = std::string;
using pii = std::pair<int, int>;
struct _ {
int v, w;
_() {}
_(int v1, int w1) {
v = v1, w = w1;
}
};
str s1, s2;
int dis[maxn];
bool vis[maxn];
std::map<str, int> t;
std::vector<_> g[maxn];
int n, m, r, cnt, x, y, w;
std::priority_queue<pii> q;
inline void add(int x, int y, int w) {
g[x].push_back(_(y, w));
return;
}
inline void Dijk(int s) {
for (int i = 1; i <= n; ++i)
dis[i] = inf, vis[i] = 0;
dis[s] = 0;
q.push(mkp(0, s));
while (!q.empty()) {
int f = q.top().second;
q.pop();
if (vis[f])
continue;
vis[f] = 1;
for (auto i : g[f]) {
if (dis[i.v] > dis[f] + i.w) {
dis[i.v] = dis[f] + i.w;
q.push(mkp(-dis[i.v], i.v));
}
}
}
return;
}
int main() {
scanf("%lld %lld", &n, &m);
while (m--) {
std::cin >> s1 >> s2;
scanf("%lld", &w);
if (!t.count(s1))
t[s1] = ++cnt;
if (!t.count(s2))
t[s2] = ++cnt;
x = t[s1], y = t[s2];
add(x, y, w);
}
scanf("%lld", &r);
while (r--) {
std::cin >> s1 >> s2;
x = t[s1], y = t[s2];
Dijk(x);
if (dis[y] == inf)
puts("Roger");
else printf("%lld\n", dis[y]);
}
return 0;
}
} // namespace XSC062
D. 道路和航线
http://222.180.160.110:1024/contest/3459/problem/4
看一眼负权边,自信认为 SPFA,然后看了一眼数据范围过后人傻了。搞不好会被卡飞啊。
我们发现无向边的权值都是正的,单独只看图中的无向边,图被分割为若干个强连通块。由于题目保证有向边没办法以任何方式反向走回来,这意味着有向边一定不处于任意一个强连通块内,也就是说,有向边起着连接这些强连通块的作用,并且在将强连通块缩点后,图中不存在环,成为 DAG。
那么现在思路就很明了了,我们对于强连通块内跑 Dij,缩点后跑 Topo 即可。
—— · EOF · ——
真的什么也不剩啦 😖