不是很難,注意積雪高度的判斷(要開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; }