图论单元检测 2+3
3 steps
首先,如果一个图中有奇环,那么这个图就不是二分图,反之就是二分图。
-
如果图中有奇环,我们通过观察可以发现任意两个点都可以连接一条边,则答案为 \(\dfrac{n\times (n-1)}{2}-m\)。
-
否则,就是给二分图,如果染白色有 \(x\) 个,染黑色有 \(y\) 个,则可以连接 \(x\times y-m\)。
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define PII pair<int, int>
#define _for(i, a, b) for (int i = (a); i <= (b); i++)
#define _pfor(i, a, b) for (int i = (a); i >= (b); i--)
const int N = 2e5 + 5;
int n, m, col[N], cnt[N];
vector<int> G[N];
void dfs(int u, int c) {
col[u] = c;
cnt[c]++;
for (auto v : G[u]) {
if (col[v] == c) {
cout << n * (n - 1) / 2 - m << endl;
exit(0);
}
if (!col[v]) dfs(v, 3 - c);
}
}
signed main() {
cin >> n >> m;
_for(i, 1, m) {
int u, v;
cin >> u >> v;
G[u].push_back(v);
G[v].push_back(u);
}
dfs(1, 1);
cout << cnt[1] * cnt[2] - m << endl;
}
gps
注意题目:一台 GPS 系统认为这条道路不是从 X 前往 N 的最短路径
,而不是 而一台 GPS 系统认为这条道路不是从 1 前往 N 的最短路径
,所以需要建出返图,倒着跑最短路。
我们肯定对两台 GPS 系统跑 2 次最短路,每次将所有可能在最短路上的边进行标记。如果一条边它被两台认为是最短路边,边权为 \(0\)。被一台认为是最短路边,边权为 \(1\)。没有被认为是最短路边,边权为 \(2\)。
最后再根据上面的边权再跑一次最短路就行了。
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define PII pair<int, int>
#define _for(i, a, b) for (int i = (a); i <= (b); i++)
#define _pfor(i, a, b) for (int i = (a); i >= (b); i--)
const int N = 2e5 + 5;
int n, m, dist[N], st[N], vis[N];
struct node {
int v, p, q;
};
vector<node> nG[N];
struct edge {
int v, op;
};
vector<edge> G[N];
struct tt {
int a, b, p, q;
}ed[N];
void dijstra1(int s) {
priority_queue<PII, vector<PII>, greater<PII> > qq;
memset(dist, 0x3f, sizeof dist);
memset(st, 0, sizeof st);
dist[s] = 0;
qq.push({0, s});
while (qq.size()) {
int u = qq.top().second; qq.pop();
if (st[u]) continue;
st[u] = 1;
for (auto e : nG[u]) {
int v = e.v, p = e.p;
if (dist[v] > dist[u] + p) {
dist[v] = dist[u] + p;
qq.push({dist[v], v});
}
}
}
}
void dijstra2(int s) {
priority_queue<PII, vector<PII>, greater<PII> > qq;
memset(dist, 0x3f, sizeof dist);
memset(st, 0, sizeof st);
dist[s] = 0;
qq.push({0, s});
while (qq.size()) {
int u = qq.top().second; qq.pop();
if (st[u]) continue;
st[u] = 1;
for (auto e : nG[u]) {
int v = e.v, q = e.q;
if (dist[v] > dist[u] + q) {
dist[v] = dist[u] + q;
qq.push({dist[v], v});
}
}
}
}
void dijstra3(int s) {
priority_queue<PII, vector<PII>, greater<PII> > qq;
memset(dist, 0x3f, sizeof dist);
memset(st, 0, sizeof st);
dist[s] = 0;
qq.push({0, s});
while (qq.size()) {
int u = qq.top().second; qq.pop();
if (st[u]) continue;
st[u] = 1;
for (auto e : G[u]) {
int v = e.v, op = e.op, d = 0;
if (op == 1 || op == 2) d = 1;
else if (op == 0) d = 2;
if (dist[v] > dist[u] + d) {
dist[v] = dist[u] + d;
qq.push({dist[v], v});
}
}
}
}
signed main() {
cin >> n >> m;
_for(i, 1, m) {
int a, b, p, q;
cin >> a >> b >> p >> q;
nG[b].push_back({a, p, q});
ed[i] = {a, b, p, q};
}
dijstra1(n);
_for(i, 1, m) { // 对p跑最短路
int a = ed[i].a, b = ed[i].b, p = ed[i].p;
if (dist[a] == dist[b] + p) {
G[a].push_back({b, 1});
vis[i] = 1;
}
}
dijstra2(n); // 对q跑最短路
_for(i, 1, m) {
int a = ed[i].a, b = ed[i].b, q = ed[i].q;
if (dist[a] == dist[b] + q) {
if (vis[i]) G[a].push_back({b, 3});
else G[a].push_back({b, 2});
vis[i] = 1;
}
}
_for(i, 1, m) {
if (!vis[i]) {
int a = ed[i].a, b = ed[i].b, q = ed[i].q;
G[a].push_back({b, 0});
}
}
dijstra3(1);
cout << dist[n] << endl;
}
Transition Game
我们对于每个 \(i\) 建立出一条 \(i\to a_i\) 的边。形成一个图。
如果在第 \(i\) 轮中, 不管先手取什么 \(k\),\(i\) 都能被写在黑板上,则说明 \(i\) 一定在环上。
topsort 可以求出环上点的个数。
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define PII pair<int, int>
#define _for(i, a, b) for (int i = (a); i <= (b); i++)
#define _pfor(i, a, b) for (int i = (a); i >= (b); i--)
const int N = 2e5 + 5;
int n, a[N], din[N], res;
vector<int> G[N];
queue<int> q;
signed main() {
cin >> n;
_for(i, 1, n) cin >> a[i], G[i].push_back(a[i]), din[a[i]]++;
_for(i, 1, n) if (!din[i]) q.push(i);
while (q.size()) {
int u = q.front(); q.pop();
res++;
for (auto v : G[u]) {
if (--din[v] == 0) q.push(v);
}
}
cout << n - res << endl;
}
Restoring Road Network
给出两两点之间的最短路,判断是否存在。如果存在,找到这张图所有边的边权之和最小值。
思路:
对于 \((i,j,k)\) 来讲,如果 \(d_{i,j}+d_{j,k}<d_{i,k}\) 就是无解。
同时,如果 \(d_{i,j}+d_{j,k}=d_{i,k}\),\((i,k)\) 这条边可以不要。
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define _for(i, a, b) for (int i = (a); i <= (b); i++)
#define _pfor(i, a, b) for (int i = (a); i >= (b); i--)
const int N = 305;
int n, a[N][N], res;
signed main() {
cin >> n;
_for(i, 1, n) _for(j, 1, n) cin >> a[i][j];
_for(k, 1, n) _for(i, 1, n) _for(j, 1, n) {
if (a[i][k] + a[k][j] < a[i][j]) puts("-1"), exit(0);
}
_for(i, 1, n) _for(j, i + 1, n) {
int flg = 0;
_for(k, 1, n) {
if (k == i || k == j) continue;
if (a[i][k] + a[k][j] == a[i][j]) {
flg = 1;
break;
}
}
if (!flg) res += a[i][j];
}
cout << res << endl;
}
Travel by car
这道题是走到一个点可以加满油,因此问题比较简单。
先跑一遍 floyd,求出两点间的最短耗油量 \(d_{i,j}\)。
再根据两两点间的耗油量,建立出一个新图。具体的,如果 \(d_{i,j}\leq L\),就意味着如果在 \(i\) 这个点加满油,是可以到达 \(j\) 的,就把新图中的 \((i,j)\) 边权赋值为 \(1\)。否则赋值为正无穷。
再对新图跑一遍最短路,减去一就是答案。(最开始的时候油是满的)
#include <bits/stdc++.h>
using namespace std;
const int N = 505;
#define PII pair<int, int>
#define int long long
#define _for(i, a, b) for (int i = (a); i <= (b); i++)
#define _pfor(i, a, b) for (int i = (a); i >= (b); i--)
int n, m, l, q, dist[N][N];
signed main() {
memset(dist, 0x3f, sizeof dist);
cin >> n >> m >> l;
_for(i, 1, m) {
int u, v, w;
cin >> u >> v >> w;
dist[u][v] = dist[v][u] = w;
}
_for(i, 1, n) dist[i][i] = 0;
_for(k, 1, n) _for(i, 1, n) _for(j, 1, n) dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j]);
_for(i, 1, n) _for(j, 1, n) {
if (i == j) dist[i][j] = 0;
else if (dist[i][j] <= l) dist[i][j] = 1;
else dist[i][j] = 0x3f3f3f3f3f3f3f3f;
}
_for(k, 1, n) _for(i, 1, n) _for(j, 1, n) dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j]);
cin >> q;
while (q--) {
int u, v;
cin >> u >> v;
if (dist[u][v] == 0x3f3f3f3f3f3f3f3f) puts("-1");
else cout << dist[u][v] - 1 << endl;
}
}
Edge Deletion
对于一条边 \((i,k)\),如果存在 \(j\) 使得 \(d_{i,j}+d_{j,k}=d_{i,k}\),\((i,k)\) 这条边可以不要。
这是一个我并不知道的 trick。
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define _for(i, a, b) for (int i = (a); i <= (b); i++)
#define _pfor(i, a, b) for (int i = (a); i >= (b); i--)
const int N = 305;
int n, m, dist[N][N], res;
struct edge {
int u, v, w;
}ed[N * N];
signed main() {
memset(dist, 0x3f, sizeof dist);
cin >> n >> m;
_for(i, 1, n) dist[i][i] = 0;
_for(i, 1, m) {
int a, b, c;
cin >> a >> b >> c;
dist[a][b] = min(dist[a][b], c);
dist[b][a] = min(dist[b][a], c);
ed[i] = {a, b, c};
}
_for(k, 1, n) _for(i, 1, n) _for(j, 1, n) dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j]);
_for(i, 1, m) {
int flg = 0;
_for(j, 1, n) {
if (j == ed[i].u || j == ed[i].v) continue;
if (dist[ed[i].u][j] + dist[j][ed[i].v] == dist[ed[i].u][ed[i].v]) {
flg = 1;
break;
}
}
if (flg) res++;
}
cout << res << endl;
}
P2149
先对 \(x1\to y1\) 求一个最短路图,再对 \(x2\to y2\) 求一个最短路图。什么边可以变为最短路图上的边?\(d_{x1,u}+w+d_{v,y1}=d_{x1,y1}\),那么 \((u,v,w)\) 就可以是最短路图上的边。
这两图的公共边,必形成一个联通块。
现在来证明一下:
假设现在这两图的公共路径形成了两个(或多个)联通块,则说明两图都可以从一个联通块到达另一个联通块。
不过由于最短路的性质,两个联通块必定会以最短路的方式连接,最终形成一个联通块,不可能形成两个及以上个联通块。
所以我们已经知道,两图的公共路径会形成一个联通块。
题目说是并行和相遇都算公共,但所求链显然不会又包括并行,又包含相遇,否则因为连通块不可能同时有 \(u\to v\) 和 \(v\to u\) 边,它不是链了。我们可以求一下只保留在两图中同向出现的边时的最长链,和只保留在两图中反向出现的边时的最长链。分别跑 topsort 即可。
#include <bits/stdc++.h>
using namespace std;
#define y1 Y1
#define PII pair<int, int>
#define _for(i, a, b) for (int i = (a); i <= (b); i++)
#define _pfor(i, a, b) for (int i = (a); i >= (b); i--)
#define int long long
const int N = 3e5 + 5;
int n, m, x1, y1, x2, y2, dist[5][N], st[N], din[N], ds[N], res;
vector<PII> G[N], nG[N];
struct node {
int u, v, w;
}ed[N];
void dijstra1(int s) {
priority_queue<PII, vector<PII>, greater<PII> > q;
q.push({0, s});
memset(st, 0, sizeof st);
dist[0][s] = 0;
while (q.size()) {
int u = q.top().second; q.pop();
if (st[u]) continue;
st[u] = 1;
for (auto e : G[u]) {
int v = e.first, d = e.second;
if (dist[0][v] > dist[0][u] + d) {
dist[0][v] = dist[0][u] + d;
q.push({dist[0][v], v});
}
}
}
}
void dijstra2(int s) {
priority_queue<PII, vector<PII>, greater<PII> > q;
q.push({0, s});
memset(st, 0, sizeof st);
dist[1][s] = 0;
while (q.size()) {
int u = q.top().second; q.pop();
if (st[u]) continue;
st[u] = 1;
for (auto e : G[u]) {
int v = e.first, d = e.second;
if (dist[1][v] > dist[1][u] + d) {
dist[1][v] = dist[1][u] + d;
q.push({dist[1][v], v});
}
}
}
}
void dijstra3(int s) {
priority_queue<PII, vector<PII>, greater<PII> > q;
q.push({0, s});
memset(st, 0, sizeof st);
dist[2][s] = 0;
while (q.size()) {
int u = q.top().second; q.pop();
if (st[u]) continue;
st[u] = 1;
for (auto e : G[u]) {
int v = e.first, d = e.second;
if (dist[2][v] > dist[2][u] + d) {
dist[2][v] = dist[2][u] + d;
q.push({dist[2][v], v});
}
}
}
}
void dijstra4(int s) {
priority_queue<PII, vector<PII>, greater<PII> > q;
q.push({0, s});
memset(st, 0, sizeof st);
dist[3][s] = 0;
while (q.size()) {
int u = q.top().second; q.pop();
if (st[u]) continue;
st[u] = 1;
for (auto e : G[u]) {
int v = e.first, d = e.second;
if (dist[3][v] > dist[3][u] + d) {
dist[3][v] = dist[3][u] + d;
q.push({dist[3][v], v});
}
}
}
}
void topsort() {
queue<int> q;
_for(i, 1, n) if (!din[i]) q.push(i);
memset(ds, 0, sizeof ds);
while (q.size()) {
int u = q.front(); q.pop();
for (auto e : nG[u]) {
int v = e.first, d = e.second;
ds[v] = max(ds[v], ds[u] + d);
res = max(res, ds[v]);
if (--din[v] == 0) q.push(v);
}
}
}
signed main() {
memset(dist, 0x3f, sizeof dist);
cin >> n >> m;
cin >> x1 >> y1 >> x2 >> y2;
_for(i, 1, m) {
int u, v, w;
cin >> u >> v >> w;
G[u].push_back({v, w});
G[v].push_back({u, w});
ed[i] = {u, v, w};
}
dijstra1(x1);
dijstra2(y1);
dijstra3(x2);
dijstra4(y2);
// puts("2222");
// cout << dist[3][x2] << endl;
_for(i, 1, n) {
for (auto e : G[i]) {
int j = e.first, w = e.second;
if (dist[0][i] + w + dist[1][j] == dist[0][y1]) {
if (dist[2][i] + w + dist[3][j] == dist[2][y2]) {
nG[i].push_back({j, w});
din[j]++;
}
}
}
}
topsort();
memset(din, 0, sizeof din);
_for(i, 1, n) nG[i].clear();
_for(i, 1, n) {
for (auto e : G[i]) {
int j = e.first, w = e.second;
if (dist[0][i] + w + dist[1][j] == dist[0][y1]) {
if (dist[3][i] + w + dist[2][j] == dist[2][y2]) {
nG[i].push_back({j, w});
din[j]++;
}
}
}
}
topsort();
cout << res << endl;
}