1. 程式人生 > >洛谷3953 NOIP2017 逛公園 最短路圖+拓撲排序+dp

洛谷3953 NOIP2017 逛公園 最短路圖+拓撲排序+dp

題目連結
題意:
給你一個n個點m條邊的有向帶權圖,設1號點到n號點的最短路是dis,給你一個k(k<=50),求所有1到n的路徑中長度不超過dis+k的數量。

題解:
顯然我們要先處理出最短路,以後再也不會寫SPFA了,因為NOI2018卡了SPFA,並且很多人也說SPFA的複雜度確實是錯的,於是就只好迪傑了。處理出最短路之後,如果k=0,就是最短路計數了。要做計數,我們不難想到要在圖上dp。我們發現只要有一個邊權全部是0的環,那麼我們的滿足題意的路徑就會有無數條,因為可以在環裡轉任意多圈之後再出來。那麼我們要判斷是否有無窮多解就是去找有沒有全部是0的環。我們可以先處理出一個最短路圖,最短路圖的含義是由所有dis[x]+dis[x][y]=dis[y]的邊連成的圖,一個性質就是如果邊權都是正數,那麼最短路圖是一個DAG。DAG是可以拓撲排序的,如果最後每個點入度都是0,就不存在權值全是0的環,否則就是有權值全是0的環,因為權值是0的邊一定不會讓兩點之間的最短路邊長,就一定會在最短路樹上,而形成環的話是沒法有其中某一個點的入度是0,因此判斷拓撲排序後的入度即可。對圖拓撲排序後根據圖的拓撲序在DAG上dp也是一個經典套路,這裡我們就會採用這個套路。因為k很小,所以很可能會出現在我們的dp狀態中,當前點肯定與dp值有關,於是我們設

dp[i][j]表示點i比dis[i]大j的路徑數,那麼對於路徑x>ydp[x][j]dp[y][j+len[x>y](dis[y]dis[x])]有貢獻。
那麼我們的做法是列舉j,然後按照拓撲序列舉x,再列舉從x出發的所有邊,進行dp。注意外層是列舉j,因為在dp的過程中如果外層列舉x的話DAG上是沒有環的,但是這裡的邊是列舉原圖的邊,所以可能之後會有環再回到x,得到答案就是錯誤的了。還有就是這個dp看似列舉的層數很多,但是其實複雜度並不高,因為所有邊都只會被列舉到一次,所有點都只會被列舉k+1(0到k)次,所以總的複雜度是
O(nk)
的。最後要說的就是,這道題在NOIP的時候是卡常數的,無數神犇都被卡了,我看了看我在洛谷上的執行速度,放在NOIP的測評機上肯定也是會超時的。
程式碼:

#include <bits/stdc++.h>
using namespace std;

int T,n,m,k,p,hed[100010],cnt,dis[100010],vis[100010];
struct node
{
    int from,to,dis,next;
}a[200010],e[200010];
int dp[1000010][51],hed2[100010],cnt2,ru[100010],xu[100010],shu;
priority_queue<pair<int
,int> > q; int que[100010],h,t,pd; long long ans; void add(int from,int to,int dis) { a[++cnt].to=to; a[cnt].from=from; a[cnt].dis=dis; a[cnt].next=hed[from]; hed[from]=cnt; } void add2(int from,int to,int dis) { e[++cnt2].to=to; e[cnt2].dis=dis; e[cnt2].next=hed2[from]; hed2[from]=cnt2; } int main() { scanf("%d",&T); while(T--) { scanf("%d%d%d%d",&n,&m,&k,&p); memset(hed,0,sizeof(hed)); memset(dis,0x3f,sizeof(dis)); memset(vis,0,sizeof(vis)); for(int i=1;i<=cnt;++i) { a[i].to=0; a[i].dis=0; a[i].next=0; } cnt=0; for(int i=1;i<=m;++i) { int x,y,z; scanf("%d%d%d",&x,&y,&z); add(x,y,z); } dis[1]=0; q.push(make_pair(0,1)); while(!q.empty()) { int x=q.top().second; q.pop(); if(vis[x]) continue; vis[x]=1; for(int i=hed[x];i;i=a[i].next) { int y=a[i].to; if(dis[y]>dis[x]+a[i].dis) { dis[y]=dis[x]+a[i].dis; q.push(make_pair(-dis[y],y)); } } } memset(hed2,0,sizeof(hed2)); memset(ru,0,sizeof(ru)); for(int i=1;i<=cnt2;++i) { e[i].to=0; e[i].dis=0; e[i].next=0; } for(int i=1;i<=cnt;++i) { if(dis[a[i].from]+a[i].dis==dis[a[i].to]) { add2(a[i].from,a[i].to,a[i].dis); ++ru[a[i].to]; } } h=1; t=0; shu=0; memset(xu,0,sizeof(xu)); for(int i=1;i<=n;++i) { if(!ru[i]) que[++t]=i; } while(h<=t) { int x=que[h]; xu[++shu]=x; for(int i=hed2[x];i;i=e[i].next) { int y=e[i].to; --ru[y]; if(!ru[y]) que[++t]=y; } ++h; } pd=0; for(int i=1;i<=n;++i) { if(ru[i]) { pd=1; break; } } if(pd==1) { printf("-1\n"); continue; } memset(dp,0,sizeof(dp)); dp[1][0]=1; for(int j=0;j<=k;++j) { for(int i=1;i<=n;++i) { int x=xu[i]; for(int l=hed[x];l;l=a[l].next) { int y=a[l].to; if(j+a[l].dis-(dis[y]-dis[x])<=k) dp[y][j+a[l].dis-(dis[y]-dis[x])]=(dp[y][j+a[l].dis-(dis[y]-dis[x])]+dp[x][j])%p; } } } ans=0; for(int i=0;i<=k;++i) ans=(ans+dp[n][i])%p; printf("%lld\n",ans); } return 0; }

PS:時隔大半年,再做NOIP題目,真是一點長進都沒有啊。這是我NOIP唯一當場爆零了的題,但是現在聽了別人講之後還是交了好幾次才過掉,雖然錯的都是小地方,但是還是看出我似乎進步並不是那麼大啊。