题目分析
首先求出从1号点出发的最短路.
我们构造一个最短路图如下.
对原图中的边(u, v, w),如果d[u] = d[v]+w,那么在最短路图中加入v->w的有向边;
如果d[v] = d[u]+w,那么在最短路图中加入u->w的有向边。
由于所有边权为正,因此有一个性质是,按上面得到的的最短路图一定是一个DAG(有向无环图)。反证法易证。
而要满足题目中的条件,我们只能从最短路图中选边,而且必须满足在得到的有向树上存在从1到每个点的路径。
那么必要条件是:对于除了1之外的每个节点,都必须选择一条它的入边,这样选出N-1条边。
可以证明这也是充分条件:这样一定能得到一棵满足要求的树。
因此答案很简单,即为除1号点外,每个点入度的乘积。
复杂度为O(M log N)(最短路复杂度)
代码
#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N = 1000;
const int M = 499500;
const int inf = 12345678;
const ll mod = (1LL<<31) - 1LL;
struct edge{//边表
int e, d;//该边重点,边权
edge* next;//下一条边
} ed[M<<1|1], *v[N+1];//边的集合ed,v[i]表示i点发出的第一条边的地址
ll c[N+1], ans;
int n, m, x, y, w, en, dist[N+1], q[N+1];
bool inq[N+1];
void add_edge(int s, int e, int d){//边表加边
ed[++en].next = v[s];
v[s] = ed+en;
ed[en].e = e;
ed[en].d = d;
}
int inc(int &x) {//循环队列
if(x+1 == n) x = 0;
else x++;
return x;
}
void spfa(int now) {//单源最短路
int front = 0, tail = 0;
for(int i = 1; i <= n; ++i) dist[i] = inf;//初始化
dist[now] = 0;
inq[now] = true;//是否在队中
q[inc(tail)] = now;
while(front != tail) {
int now = q[inc(front)];
inq[now] = false;
for(edge* e = v[now]; e; e = e->next) {//遍历从now出发的所有边
if(dist[e->e] > dist[now]+e->d) {
dist[e->e] = dist[now]+e->d;//松弛操作
if(!inq[e->e]) {
inq[e->e] = true;
q[inc(tail)] = e->e;//新点入队
}
}
}
}
}
int main() {
scanf("%d%d", &n, &m);
for(int i = 1; i <= m; ++i) {
scanf("%d%d%d", &x, &y, &w);
add_edge(x, y, w);
add_edge(y, x, w);
}
spfa(1);//初始化
int front = 0, tail = 0;
q[inc(tail)] = 1;//从1号点开始遍历
inq[1] = true;
c[1] = 1LL;//点i的入度为c[i]
while(front != tail) {
int now = q[inc(front)];
for(edge* e = v[now]; e; e = e->next) {//遍历该点能到的所有点
if(dist[e->e] == dist[now]+e->d) {//到该点的距离正好为最短路
++c[e->e];//该点入度更新
if(c[e->e] >= mod) c[e->e] -= mod;//入度取模
if(!inq[e->e]) {//下一点进队
q[inc(tail)] = e->e;
inq[e->e] = true;
}
}
}
}
ans = 1LL;
for(int i = 1; i <= n; ++i) {
ans *= c[i];//所有入度乘积
if(ans >= mod) ans %= mod;
}
printf("%d\n", (int)ans);
}