NC14501 大吉大利,晚上吃鸡!

2023-03-01,,

题目链接

题目

题目描述

最近《绝地求生:大逃杀》风靡全球,皮皮和毛毛也迷上了这款游戏,他们经常组队玩这款游戏。

在游戏中,皮皮和毛毛最喜欢做的事情就是堵桥,每每有一个好时机都能收到不少的快递。

当然,有些时候并不能堵桥,皮皮和毛毛会选择在其他的必经之路上蹲点。

K博士作为一个老年人,外加有心脏病,自然是不能玩这款游戏的,但是这并不能妨碍他对这款游戏进行一些理论分析,比如最近他就对皮皮和毛毛的战士很感兴趣。

游戏的地图可以抽象为一张 n 个点 m 条无向边的图,节点编号为 1 到 n ,每条边具有一个正整数的长度。

假定大魔王都会从 S 点出发到达 T 点( S 和 T 已知),并且只会走最短路,皮皮和毛毛会在 A 点和 B 点埋伏大魔王。

为了保证一定能埋伏到大魔王,同时又想留大魔王一条生路,皮皮和毛毛约定 A 点和 B 点必须满足:

\1. 大魔王所有可能路径中,必定会经过 A 点和 B 点中的任意一点

\2. 大魔王所有可能路径中,不存在一条路径同时经过 A 点和 B 点

K博士想知道,满足上面两个条件的 A,B 点对有多少个,交换 A,B 的顺序算相同的方案。

输入描述

第一行输入四个整数 n,m,S,T(\(1≤n≤5×10^4,1≤m≤5×10^4,1≤S,T≤n\)),含义见题目描述。

接下来输入 m 行,每行输入三个整数 u,v,w(\(1≤u,v≤n,1≤w≤10^9\))表示存在一条长度为 w 的边链接 u 和 v 。

输出描述

输出一行表示答案。

示例1

输入

7 7 1 7
1 2 2
2 4 2
4 6 2
6 7 2
1 3 2
3 5 4
5 7 2

输出

6

说明

合法的方案为 <2,3>,<2,4>,<4,3>,<4,5>,<6,3>,<6,5>。

备注

\(1≤n≤5×10^4,1≤m≤5×10^4,1≤w≤10^9\)

题解

知识点:最短路,拓扑排序,计数dp。

这道题分几步走:

    跑正反两次最短路,得到 \(f\) ,再求经过每个点 \(u\) 的最短路条数 \(ff[u] = f[0][u]\cdot f[1][u]\) ,不在最短路上的点应为 \(0\) 。同时,记录方案数到点的映射 \(mp[ff[u]][u] = 1\) 方便最后统计。注意不在最短路上的点也要统计,即 \(mp[0][u] = 1\) ,因为两个点可以有一个点不在最短路上,而另一个点通过了所有最短路。因为方案数本身过大,所以取了模,虽然很玄学,但能过。
    根据第一步得到的 \(dis\) 新建一个最短路DAG图,即只包括最短路上的边且是单向的。在最短路图上跑一边拓扑排序得到拓扑序。对于每个点 \(u\) ,根据拓扑序求出可以从起点开始经过哪些点到达 \(tran[0][u]\),以及拓扑逆序求出经过哪些点到达终点 \(tran[1][u]\) 。这部分用bitset 实现刚刚好。
    统计对于每个点 \(u\) 满足以下两个条件的点 \(v\) :\(S\) 经过 \(u\) 到 \(T\) 都不可能被经过,并且 \(ff[u] + ff[v] = ff[T]\) 。前者保证 \(u,v\) 不出现在一条最短路上满足条件2,后者保证 \(u,v\) 不重不漏的划分了所有路径,即从 \(S\) 到 \(T\) 必定经过 \(u,v\) 满足条件1。

最后,因为交换A,B顺序算一个答案,所以统计的答案除以 \(2\) 。

注意,有可能 \(S\) 不能到达 \(T\) ,即 \(ff[T] = 0\) ,此时应该A,B可以是任意点,答案是 \(\frac{n(n-1)}{2}\) 直接输出。因为最短路图建不成,所以不能继续走后面的步骤,应该直接输出。

时间复杂度 \(O((n+m)\log m) + O(n+m) + O(nm)\)

空间复杂度 \(O(n+m)\)

代码

#include <bits/stdc++.h>
#define ll long long using namespace std; template<class T>
struct Graph {
struct edge {
int v, nxt;
T w;
};
int idx;
vector<int> h;
vector<edge> e; Graph(int n, int m) :idx(0), h(n + 1), e(m + 1) {}
void init(int n) {
idx = 0;
h.assign(n + 1, 0);
} void add(int u, int v, T w) {
e[++idx] = edge{ v,h[u],w };
h[u] = idx;
}
};
const int N = 50007, M = 50007 << 1, mod = 1e9 + 7;
Graph<int> g(N, M), g2(N, M);
int n, m; vector<vector<ll>> dis(2, vector<ll>(N));
vector<vector<int>> f(2, vector<int>(N));
vector<int> ff(N);
map<int, bitset<N>> mp; void dijkstra(int st, vector<ll> &dis, vector<int> &f) {
dis.assign(n + 1, 0x3f3f3f3f3f3f3f3f);
vector<bool> vis(n + 1, false);
struct node {
int v;
ll w;
bool operator<(const node &a)const {
return w > a.w;
}
};
priority_queue<node> pq; dis[st] = 0;
f[st] = 1;
pq.push(node{ st,0 });
while (!pq.empty()) {
int u = pq.top().v;
pq.pop();
if (vis[u]) continue;
vis[u] = 1;
for (int i = g.h[u];i;i = g.e[i].nxt) {
int v = g.e[i].v, w = g.e[i].w;
if (dis[v] > dis[u] + w) {
dis[v] = dis[u] + w;
pq.push(node{ v,dis[v] });
f[v] = f[u];
}
else if (dis[v] == dis[u] + w) {
f[v] = (f[v] + f[u]) % mod;
}
}
}
} vector<int> topo;
void toposort() {
vector<int> deg(n + 1, 0);
queue<int> q;
for (int i = 1;i <= g2.idx;i++) deg[g2.e[i].v]++;
for (int i = 1;i <= n;i++) if (!deg[i]) q.push(i);
while (!q.empty()) {
int u = q.front();
topo.push_back(u);
q.pop();
for (int i = g2.h[u];i;i = g2.e[i].nxt) {
int v = g2.e[i].v;
deg[v]--;
if (!deg[v]) q.push(v);
}
}
} bitset<N> tran[2][N]; int main() {
std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int S, T;
cin >> n >> m >> S >> T;
for (int i = 1;i <= m;i++) {
int u, v, w;
cin >> u >> v >> w;
g.add(u, v, w);
g.add(v, u, w);
} //分别得到S,T到各个点的最短路和方案数
dijkstra(S, dis[0], f[0]);
dijkstra(T, dis[1], f[1]); if (f[0][T] == 0)//注意判断不连通的情况(等价于f[1][s]==0)
{
cout << 1LL * n * (n - 1) / 2 << '\n';//A可能情况*B可能情况/2(防止重复)
return 0;
} //在最短路上的点计算总条数,其他为0;并且统计一个方案数对应的点,方案数取模因为太大,不会被卡
for (int u = 1;u <= n;u++) {
if (dis[0][u] + dis[1][u] == dis[0][T])
ff[u] = 1LL * f[0][u] * f[1][u] % mod;
mp[ff[u]][u] = 1;
} //用最短路上的边建图
for (int u = 1;u <= n;u++) {
for (int i = g.h[u];i;i = g.e[i].nxt) {
int v = g.e[i].v, w = g.e[i].w;
if (dis[0][u] + dis[1][v] + w == dis[0][T]) g2.add(u, v, w);
}
} //得到最短路遍历顺序
toposort(); //分别获得起点传递,终点逆传递
for (int i = 0;i < n;i++) {
int u = topo[i];
tran[0][u][u] = 1;
for (int j = g2.h[u];j;j = g2.e[j].nxt) {
int v = g2.e[j].v;
tran[0][v] |= tran[0][u];
}
}
for (int i = n - 1;i >= 0;i--) {
int u = topo[i];
tran[1][u][u] = 1;
for (int j = g2.h[u];j;j = g2.e[j].nxt) {
int v = g2.e[j].v;
tran[1][u] |= tran[1][v];
}
} //计算答案:两点不能在同一条最短路上,即g2中没有传递性;最短路至少通过一点,即两点方案数之和等于总方案数
ll ans = 0;
for (int u = 1;u <= n;u++) {
ans += ((~(tran[0][u] | tran[1][u])) & mp[(ff[T] - ff[u] + mod) % mod]).count();
}
cout << ans / 2 << '\n';
return 0;
}

NC14501 大吉大利晚上吃鸡!的相关教程结束。

《NC14501 大吉大利,晚上吃鸡!.doc》

下载本文的Word格式文档,以方便收藏与打印。