题目
题意概要
给出
n
n
n 个点
m
m
m 条边的简单无向图, 求出每一对
s
,
t
(
1
⩽
s
<
t
⩽
n
)
s,t\;(1\leqslant s<t\leqslant n)
s,t(1⩽s<t⩽n) 的所有最短路的边集的 并集 的大小。即:有多少条边 可能 是
s
,
t
s,t
s,t 最短路上的边。
注意,你要输出 n ( n − 1 ) 2 n(n-1)\over 2 2n(n−1) 个数字,每个数字可能高达 m m m 。
数据范围与提示
2
⩽
n
⩽
500
2\leqslant n\leqslant 500
2⩽n⩽500 而
0
⩽
m
⩽
n
(
n
−
1
)
2
0\leqslant m\leqslant \frac{n(n-1)}{2}
0⩽m⩽2n(n−1) 。
思路
为什么是蓝题?我觉得这道题很难啊……或者我脖子上的东西不太好使。
最初考虑枚举起点,这是 O ( n ) \mathcal O(n) O(n) 的。建出最短路图,然后用递推, f ( x ) = ∑ ⟨ y , x ⟩ f ( y ) f(x)=\sum_{\langle y,x\rangle}f(y) f(x)=∑⟨y,x⟩f(y) 。但是这样是会算重的!因为这是 DAG \texttt{DAG} DAG,而不是树。
然后又考虑用 b o o l \tt bool bool 数组存下每个点的可能边。没错,一个 O ( n m ) \mathcal O(nm) O(nm) 的数组。然后将其沿着最短路图上的边 “传导” 。由于最短路图上可能有 O ( m ) \mathcal O(m) O(m) 条边,每次转移是 O ( m ) \mathcal O(m) O(m) 的,于是时间复杂度应为 O ( m 2 ) \mathcal O(m^2) O(m2) 的……
突然灵机一动,从上面的做法中找到了优化点。回忆上面的过程,对于一条边,将其到达端点上打一个标记,然后往后推。这就是说,如果两条边的末端点相同,带来的影响是一样的!
所以上面的 b o o l \tt bool bool 数组可以对点进行标记,表示第 x x x 点作为末端点的所有边是否已经被统计到。所以变成了 O ( n 2 ) \mathcal O(n^2) O(n2) 的数组。此时大力后推,就是 O ( n m ) \mathcal O(nm) O(nm) 的复杂度了。再算一算每个点作为结束端点的边数量即可。
怎么求出每个点作为结束端点的边数量?直接 F l o y d \rm Floyd Floyd 求最短路,然后可以使用距离数组进行判断啦。
某种意义上来说,我是对动态规划进行了优化,而不是图论做法。
代码
#include <cstdio>
#include <iostream>
#include <vector>
#include <algorithm>
#include <queue>
#include <bitset>
using namespace std;
typedef long long int_;
inline int readint(){
int a = 0; char c = getchar(), f = 1;
for(; c<'0'||c>'9'; c=getchar())
if(c == '-') f = -f;
for(; '0'<=c&&c<='9'; c=getchar())
a = (a<<3)+(a<<1)+(c^48);
return a*f;
}
const int MaxN = 505;
const int MaxM = MaxN*MaxN;
int n, m;
struct Edge{
int to, nxt, val;
Edge(){ }
Edge(int T,int N,int V){
to = T, nxt = N, val = V;
}
};
Edge e[MaxM];
int head[MaxN], cntE;
/** @param save If true, keep origin. */
void clear_graph(bool save = false){
for(int i=1; i<=n; ++i)
head[i] = -1;
if(!save) cntE = 0;
}
/** @brief add a directed edge */
void addEdge(int a,int b,int c){
e[cntE] = Edge(b,head[a],c);
head[a] = cntE ++;
}
const int infty = (1<<30)-1;
int g[MaxN][MaxN], f[MaxN][MaxN];
int cnt[MaxN], ans[MaxN];
int main(){
n = readint(), m = readint();
for(int i=1,a,b; i<=m; ++i){
a = readint(), b = readint();
g[a][b] = g[b][a] = readint();
}
for(int i=1; i<=n; ++i)
for(int j=1; j<=n; ++j)
if(g[i][j] || i == j)
f[i][j] = g[i][j];
else f[i][j] = infty;
for(int k=1; k<=n; ++k)
for(int i=1; i<=n; ++i)
for(int j=1; j<=n; ++j)
f[i][j] = min(f[i][j],f[i][k]+f[k][j]);
for(int i=1; i<n; ++i){
for(int j=1; j<=n; ++j)
cnt[j] = ans[j] = 0;
for(int k=1; k<=n; ++k)
for(int j=1; j<=n; ++j)
if(g[k][j]) // 存在这条边
if(f[i][j] == f[i][k]+g[k][j])
++ cnt[j]; // 以 j 结尾
for(int k=1; k<=n; ++k)
for(int j=i+1; j<=n; ++j)
if(f[i][j] == f[i][k]+f[k][j]){
ans[j] += cnt[k];
}
for(int j=i+1; j<=n; ++j)
printf("%d ",ans[j]);
}
return 0;
}