NOIP2017D1T3逛公園——哎呦!
#include<cstdio> #include<cstring> #define MXN 100001 #define MXM 200001 #define MXK 51 int afst[MXM],anxt[MXM],av[MXM],aw[MXM]; int bfst[MXM],bnxt[MXM],bv[MXM],bw[MXM]; int dis[MXN],queue[10*MXN]; int f[MXN][MXK],vis[MXN][MXK]; int n,m,k,p,t,etop,ans; bool onzer,zero[MXN]; int read(){ int x=0逛公園,w=1; char c=getchar(); while(c<‘0‘||c>‘9‘){ if(c==‘-‘) w=-1; c=getchar(); } while(c>=‘0‘&&c<=‘9‘){ x=(x<<3)+(x<<1)+(c-‘0‘); c=getchar(); } return x*w; } void add(int x,int y,int z){ anxt[++etop]=afst[x]; afst[x]=etop; av[etop]=y; aw[etop]=z; bnxt[etop]=bfst[y]; bfst[y]=etop; bv[etop]=x; bw[etop]=z; return; } void spfa(){ int head=0,tail=0; queue[tail++]=1; while (head!=tail){ int now=queue[head]; for (int i=afst[now];i!=-1;i=anxt[i]){ int j=av[i];if (dis[now]+aw[i]<dis[j]){ dis[j]=dis[now]+aw[i]; if (!zero[j]){ zero[j]=1; queue[tail]=j; if(tail+1!=10*MXN) tail++; else tail=0; } } } if(head+1!=10*MXN) head++; else head=0; zero[now]=0; } return; } int ddf(int i,int h){ if(~f[i][h])return f[i][h]; vis[i][h]=1; f[i][h]=0; for(int y=bfst[i];y!=-1;y=bnxt[y]){ int j=bv[y],t=dis[i]-dis[j]+h-bw[y]; if(t<0)continue; if(vis[j][t]) onzer=0; f[i][h]+=ddf(j,t); f[i][h]%=p; } vis[i][h]=0; return f[i][h]; } void init(){ n=read(); m=read(); k=read(); p=read(); etop=0; memset(afst,-1,sizeof(afst)); memset(anxt,-1,sizeof(anxt)); memset(av,0,sizeof(av)); memset(aw,0,sizeof(aw)); memset(bfst,-1,sizeof(afst)); memset(bnxt,-1,sizeof(anxt)); memset(bv,0,sizeof(av)); memset(bw,0,sizeof(aw)); memset(vis,0,sizeof(vis)); memset(zero,0,sizeof(zero)); memset(f,-1,sizeof(f)); for(int i=1;i<=n;i++) dis[i]=0x6fffffff; for(int i=1;i<=m;i++){ int x=read(),y=read(),z=read(); add(x,y,z); } zero[1]=1; dis[1]=0; ans=0; onzer=1; f[1][0]=1; return; } int main(){ t=read(); while (t--){ init(); spfa(); for(int i=0;i<=k;i++){ ans+=ddf(n,i); ans%=p; } ddf(n,k+1); if(!onzer) printf("-1\n"); else printf("%d\n",ans); } return 0; }
這真是一道神奇的題目。
首先必須說明,這是我看了題解後寫的。憑本人的水平是絕對A不了這道題的(畢竟考試時兩天兩道DP一道都沒有看出來(或者說至少沒有試著去寫))。
不過這道題思路還是比較明確的:
首先為了得知d的值無論如何都要跑一邊最短路來求(本人用的SPFA,但是自己覺得dj更好)。之後純粹暴力的想法就是dfs,到達n之後判斷這條路徑上的權值和是否滿足條件,dfs中判斷路徑上的0環,順便用<=d+K特判(並且有些0環對答案並沒有影響)。稍微思索一下就明白這樣的時間復雜度為O(跑不過)。
上面提到的不重要0環可以用正反兩次最短路,記下兩個dis數組來判斷這一點的0環是否對答案有貢獻(意為:在點i處發現一個0環,利用dis1[i]+dis2[i]<=d+K判斷,成立則0環對最終方案有影響(明顯若不等號不成立則方案中不存在經過i點的路徑,則此時暫時不用考慮這個0環))。
通過觀察到K<=50,認為可以DP,其中某一個狀態與K有關。狀態f[i][j]表示路徑上點i,此路徑超過最短路長度為j。
狀態轉移方程可得:f[i][j]=Σf[u][t] ((u,i) in G,t=dis[i]-dis[u]+j-w[i,u])。
其中,t=dis[i]-dis[u]+j-w[i,u]的方程計算經過(u,i)路徑時,若i點時權值和超過j,則u點時權值和超過t。整理前等式像這樣子:t+(dis[u]+w[i,u]-dis[i])=j。
因此明顯可以進行容易的DP,復雜度為O(MK)。以上方程適用於反向DP,初始化為f[1][0]=1,答案ans=∑f[n][i](0<=i<=K)。
接下來繼續處理判斷0環:
第一種方法是像前面某一段提到的,由於是圖上DP,狀態隨dfs轉移,因此利用一個vis數組,當dfs遇到vis為真時,利用dis1[i]+dis2[i]<=n+K判斷0環是否影響答案,影響則輸出-1,否則繼續得佛斯;
第二種方法是我抄了題解的:dfs時再用vis[i][j]記錄之前是否出現過同樣的狀態,出現過則輸出-1。
實際上最後還額外進行了一次dfs(n,K+1),由於題解寫了,並且刪了樣例都過不了,就交了。據同機房大神說,是因為f[1][0]置為1,所以需要這樣搞(同機房另一大神親身經歷告訴我們,刪了這句話,樣例不過,照樣A題)。
由於狀態保存的是嚴格到達i點路徑超過最短路j,因此保證上面那一段對0環判斷的有效性,並且(貌似?)沒有什麽後效性(據機房另一位大神透露,後效性來自轉一圈後回到原點,又沒有超限,下次從n開始dfs,還會dfs到這個狀態,導致方案重復。代碼裏這樣搞貌似不會這樣,大約是因為第二個狀態是順著推,又在圖上倒著推導致的?)。不過我不會證,就不能說什麽了。
說一點題外話:
看題解時發現一些小技巧,比如數組模擬鄰接表可以只開四個數組,還有代碼裏SPFA手打隊列時對head++和tail++的處理,保證了不會RE,還有貌似可以(f+=d)%=p這樣寫?不過我沒有這樣。並且還發現2147483647==0x7fffffff,但是INF設成這個加減運算時會出問題,我設成0x6fffffff。
順便一說剛學到head++tail++防RE的技巧時還用錯了,反而導致十個點全RE了......我是真的菜,發現這一點時大喊哎呦。
就這樣吧。
PS:
最近真的是遇到許多玄學RE,並且還一直調不對。今天下午調這題時dfs的參數還會莫名改變,明明所有邊權值都是0,第二個狀態還能搜到1?我是真的服。
NOIP2017D1T3逛公園——哎呦!