不是很难,注意积雪高度的判断(要开long long
)以及终点不须要特判便可。php
#include<cstdio> #include<cstring> const int maxn=1e5+2; struct Solution { struct Edge{int from,to,len;}; struct Graph { Edge edges[maxn*10]; int edge_cnt,Head[maxn],Next[maxn*10]; void add_edge(int from,int to,int len) { edges[++edge_cnt]=(Edge){from,to,len}; Next[edge_cnt]=Head[from],Head[from]=edge_cnt;return; } }G; int bh[maxn],lh[maxn],ad; long long dis[maxn],vis[maxn]; int S,T; int Q[maxn*10]; void SPFA() { memset(dis,0x3f,sizeof(dis)); dis[S]=0;int h=0,t=0;Q[t++]=S; while( h!=t ) { int p=Q[h++];h%=maxn*5; vis[p]=0; for(int i=G.Head[p];i;i=G.Next[i]) { Edge e=G.edges[i]; if( ( e.to==T or bh[e.to]+ad*( (long long)dis[p]+e.len )<=lh[e.to] ) and dis[p]+(long long)e.len<dis[e.to] ) { dis[e.to]=dis[p]+e.len; if( !vis[e.to] ) vis[e.to]=1,Q[t++]=e.to,t%=maxn*5; } } } return; } void solve() { int n,m,tl;scanf("%d%d%d%d%d%d",&n,&m,&S,&T,&tl,&ad); for(int i=1;i<=n;i++) scanf("%d%d",&bh[i],&lh[i]); for(int i=1;i<=m;i++) { int x,y,l;scanf("%d%d%d",&x,&y,&l); G.add_edge(x,y,l),G.add_edge(y,x,l); } SPFA(); if( dis[T]<=tl ) printf("%d",dis[T]); else printf("wtnap wa kotori no oyatsu desu!"); return; } }SOL; int main() { SOL.solve();return 0; }
这个有点意思。算法
最短距离不难求,可是怎么求方案数呢?数组
if( dis[s][i]+dis[i][t]==dis[s][t] ) ans[s][t]+=ans[s][i]*ans[i][t];
上面的式子不难理解,可是实际题目中的写法必须是下面这样:this
for(int i=1;i<=n;i++) for(int s=1;s<=n;s++) for(int t=1;t<=n;t++) if( s!=i and i!=t and s!=t ) { if( dis[s][i]+dis[i][t]<dis[s][t] ) { dis[s][t]=dis[s][i]+dis[i][t]; ans[s][t]=ans[s][i]*ans[i][t]; } else if( dis[s][i]+dis[i][t]==dis[s][t] ) ans[s][t]+=ans[s][i]*ans[i][t]; }
也就是说,咱们要边求最短路边统计方案数。spa
为何要这样呢?难道我不能够求完最短路再求方案数吗?code
不能够。get
上面的例子,算完最短路以后,就会有两种“可行”的方案:string
问题在于:对于有多个节点的最短路径,咱们要找到表示它的惟一方法。it
这也就是为何要在求最短路的时候同时求方案数。io
假设如今首先以\(2\)为中继点,那么咱们就只能使\(1\to 3\)的方案数等于\(1\),至于\(2\to 4\)咱们要不已经维护过了,要不就是维护不了的(由于不能把路径分解为\(2\to 2\)和\(2\to 4\),图上没有自环,并且当前\(2\to 4\)尚未被维护到)
而后,再枚举到以\(3\)为中继点的时候,就能够用\(1\to 3\)和\(3\to 4\)来表示\(1\to 4\)这条路径了。
(不用担忧重复,重复的话意味着\(1\to 2\)和\(2\to 4\)也被统计了,可是这里以\(2\)为中继点的时候,咱们尚未维护\(2\to 4\)啊,并且维护完\(2\to 4\)的时候咱们也不会再去枚举回\(2\)了,因此不会重复)
扯了这么多,就是为了说明:要在求最短路的同时求方案数。
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; const int maxn=102,maxm=9002; struct Solution { int dis[maxn][maxn]; long long ans[maxn][maxn]; void solve() { memset(dis,0x3f,sizeof(dis)); int n,m;scanf("%d%d",&n,&m); for(int i=1;i<=m;i++) { int x,y,l;scanf("%d%d%d",&x,&y,&l); dis[x][y]=l,ans[x][y]=1; dis[y][x]=l,ans[y][x]=1; } for(int i=1;i<=n;i++) for(int s=1;s<=n;s++) for(int t=1;t<=n;t++) if( s!=i and i!=t and s!=t ) { if( dis[s][i]+dis[i][t]<dis[s][t] ) { dis[s][t]=dis[s][i]+dis[i][t]; ans[s][t]=ans[s][i]*ans[i][t]; } else if( dis[s][i]+dis[i][t]==dis[s][t] ) ans[s][t]+=ans[s][i]*ans[i][t]; } for(int i=1;i<=n;i++) { double tmp=0.0; for(int s=1;s<=n;s++) { for(int t=1;t<=n;t++) { if( s==i or i==t or s==t ) continue; if( dis[s][i]+dis[i][t]==dis[s][t] ) tmp+=( (double)ans[s][i]*ans[i][t] )/ans[s][t]; } } printf("%.3lf\n",tmp); } return; } }SOL; int main() { SOL.solve();return 0; }
其实理解成矩阵就很好理解了:
\(A\)矩阵和\(B\)矩阵分别表示通过\(x\)条边和\(y\)条边的矩阵,那么\(C=A\times B\)就是通过\(x+y\)条边的矩阵。
JZ res; for(int i=1;i<=idx_cnt;i++) for(int s=1;s<=idx_cnt;s++) for(int t=1;t<=idx_cnt;t++) res.a[s][t]=std::min( res.a[s][t],a[s][i]+op.a[i][t] ); return res;
和普通的\(Floyd\)算法不一样的是,在这里,等式两边的矩阵是相互独立的。也就是说,\(C\)矩阵中的\(dis\)不会对\(A\)和\(B\)当中的产生影响,因此\(C\)矩阵是切切实实只表示\(x+y\)条边的矩阵。
而\(Floyd\)算法中,用\(1\)条边的信息统计完\(2\)条边的信息以后,\(2\)条边的信息又要立刻在\(dis\)数组里面用来统计\(3\)条、\(4\)条边的信息,因此不是独立的。
知道具体含义以后就能够矩阵快速幂了:
#include<cstdio> #include<cstring> #include<algorithm> int idx[1002],idx_cnt; struct JZ { int a[105][105]; JZ(){memset(a,0x3f,sizeof(a));} JZ operator * (const JZ &op) { JZ res; for(int i=1;i<=idx_cnt;i++) for(int s=1;s<=idx_cnt;s++) for(int t=1;t<=idx_cnt;t++) res.a[s][t]=std::min( res.a[s][t],a[s][i]+op.a[i][t] ); return res; } JZ operator *= (const JZ &op) { *this = (*this)*op; return *this; } }; int main() { JZ dis; int K,m,S,T;scanf("%d%d%d%d",&K,&m,&S,&T); for(int i=1;i<=m;i++) { int len,x,y;scanf("%d%d%d",&len,&x,&y); if( !idx[x] ) idx[x]=++idx_cnt; if( !idx[y] ) idx[y]=++idx_cnt; dis.a[idx[x]][idx[y]]=dis.a[idx[y]][idx[x]]=len; } JZ ans=dis;--K; while(K) { if( K&1 ) ans*=dis; dis*=dis;K>>=1; } printf("%d",ans.a[idx[S]][idx[T]]); return 0; }
最短路+计算几何是真的神奇。
首先,那些所在直线会穿过被保护节点(把它们分红两部分)的线段是不能建边的(要不就是真的穿过了,要不就是没有穿过,可是方向不对,选了以后会多绕一条线段)
而后就要单向建边。
例如,若是对于\(\overrightarrow{AB}\),全部被保护节点都在它的右边(也能够与它共线),那么就能够直接从\(A\)连一条边到\(B\)。
其实这样作是为了求有向图最小环(无向图的太难了,偷懒嘛)。
有向图最小环就很简单,直接\(Floyd\)以后,\(dis(i,i)\)就是通过\(i\)点的最小环的长度。
无向图最小环另外讲。
#include<cstdio> #include<cstring> #include<algorithm> const int maxn=502; struct Solution { struct Point { int x,y; Point(int x=0,int y=0){this->x=x,this->y=y;} void read(){scanf("%d%d",&x,&y);return;} }; typedef Point Vector; Vector make_vec(Point s,Point t){return Vector(t.x-s.x,t.y-s.y);} int Cross(Vector A,Vector B){return A.x*B.y-A.y*B.x;} Point P1[maxn],P2[maxn]; int dis[maxn][maxn]; bool solve() { memset(dis,0x3f,sizeof(dis)); int n1;if( scanf("%d",&n1)==EOF ) return 0; for(int i=1;i<=n1;i++) P1[i].read(); int n2;scanf("%d",&n2); for(int i=1;i<=n2;i++) P2[i].read(); for(int s=1;s<=n2;s++) for(int t=1;t<=n2;t++) { if( s==t ) continue; bool flag=1; for(int k=1;k<=n1;k++) if( Cross( make_vec( P2[s],P2[t] ),make_vec( P2[s],P1[k] ) )>0 ){flag=0;break;} if(flag) dis[s][t]=1; } for(int i=1;i<=n2;i++) for(int s=1;s<=n2;s++) { if( dis[s][i]==0x3f3f3f3f ) continue; for(int t=1;t<=n2;t++) dis[s][t]=std::min( dis[s][t],dis[s][i]+dis[i][t] ); } int ans=0x3f3f3f3f; for(int i=1;i<=n2;i++) ans=std::min( ans,dis[i][i] );; if( ans>n2 ) puts("ToT"); else printf("%d\n",n2-ans); return 1; } }SOL; int main() { while( SOL.solve() );return 0; }