[暑假集训Day1T1]黑暗城堡
作者:互联网
因为D[i]表示i号节点到1号节点的最短路径,所以可以先以1为源点跑一边SPFA,预处理出每个点到1号节点的最短路。之后开始考虑所谓的“最短路径生成树”,在这棵生成树中有以下性质:当fa[i]==node时,必满足dist[node]+w(node,i)=dist[node],但dist[node]+w(node,i)==dist[node]时,node不一定是i的父节点,因为图的最短路可能有多条。因此我们记录mul[i]为i的前驱个数,也就是所有满足dist[k]+w(k,i)==dist[i]的点的个数。根据乘法原理累计相乘即可求出答案。
常见疑问:
Q1:为什么这样构造出来一定是一棵树???
A1:因为对于每一棵生成树,除1号节点外,都有一个唯一的前驱,我们假想他们之间连了一条边,则连了(n-1)条边,并且保证联通性,根据树的定义,可以保证这是一棵树。
Q2:为什么可以不排序???网上大神排序出于什么目的???
A2:因为边权均为正,所以比较dist值较大的节点只能从dist值较小的节点走过来,因此对i号节点统计时我们是用不到dist值比i大的节点的,因此使用O(NlogN)的时间按每个节点dist值进行排序,即可省去一半的时间。注意,不排序对正确性没有影响。
Q3:为什么不用伟大的Dijkstra堆优化而使用已经死了的SPFA???
A3:LQX学长调他的堆优化调了一个下午。
参考代码如下:
#include<iostream> #include<algorithm> #include<cstdio> #include<queue> #define int long long #define mod 2147483647 #define N 1005 #define M 1000005 #define INF 110412365 using namespace std; struct node { int num,dist; }point[N]; int n,m,v[M],w[M],head[M],nxt[M],cnt,x,y,z,mul[N]; bool vis[N]; void add(int a,int b,int c) { v[++cnt]=b; w[cnt]=c; nxt[cnt]=head[a]; head[a]=cnt; } int read() { int x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();} return x*f; } void spfa(int s) { for(int i=2;i<=n;i++)point[i].dist=INF; queue<int>q; q.push(s); vis[s]=1; while(!q.empty()) { int c=q.front(); q.pop(); vis[c]=0; for(int i=head[c];i;i=nxt[i]) { int y=v[i]; if(point[y].dist>point[c].dist+w[i]) { point[y].dist=point[c].dist+w[i]; if(!vis[y]) { q.push(y); vis[y]=1; } } } } } bool cmp(node a,node b) { return a.dist<b.dist; } signed main() { n=read();m=read(); for(int i=1;i<=m;i++) { x=read();y=read();z=read(); add(x,y,z);add(y,x,z); } spfa(1); for(int i=1;i<=n;i++) { for(int j=head[i];j;j=nxt[j]) { if(point[i].dist+w[j]==point[v[j]].dist)mul[v[j]]++; } } int ans=1; for(int i=2;i<=n;i++)ans*=mul[i],ans%=mod; cout<<ans<<endl; return 0; }
标签:node,cnt,ch,dist,Day1T1,节点,int,暑假,集训 来源: https://www.cnblogs.com/szmssf/p/11153996.html