我看了CBD的題解才知道怎麼處理重複走的路徑
不過來推出的定義不太一樣,請容我修改一下方便理解(以下部分內容擷取自原文)
考慮DP[a][b]代表從1走到A加上從B走回1的路上最少可以經過幾個節點(為了方便轉移,如果A=B,A點「算兩次」),那麼所求的答案就是DP[2][2]
首先我們可以用Floyd求出任兩點之間的最短路(之後記為DIST[][])
由第一個範測可以觀察到:
如果我們任取四個點a,b,x,y
DP[x][y]的定義為1->x和y->1路徑的最小總點數
可以用1->a->b->x和y->a->b->1這兩條路徑來更新
也就是說DP[x][y]可以用DP[a][b]+(DIST[a][b]-1)+DIST[b][x]+DIST[y][a]來更新
然後將(1,1)這個點加進queue,去鬆弛其他點,只有被鬆弛而且不在queue裡面的點才加進queue裡面
SPFA理論複雜度最差為O(VE),上述狀態表示方式有N^2個點、N^4條邊,因此複雜度O(N^6)
但一般情況下每個點平均進出queue兩次,所以執行時間趨近O(N^4)
感覺測資再出硬一點就TLE了=ㄦ=
code:
#include<cstdio> #include<vector> #include<queue> #include<cassert> using namespace std; const int INF=2147483647; void getmin(int &a,const int b){if(b<a)a=b;} int N,M,DIST[100][100],DP[100][100]; int main() { // freopen("in.txt","r",stdin); while(scanf("%d%d",&N,&M)==2) { for(int i=0;i<N;i++)for(int j=0;j<N;j++)DIST[i][j]=DP[i][j]=INF; for(int i=0;i<N;i++)DIST[i][i]=0; for(int i=0,a,b;i<M;i++)scanf("%d%d",&a,&b),a--,b--,getmin(DIST[a][b],1); for(int m=0;m<N;m++) { for(int a=0;a<N;a++)if(DIST[a][m]!=INF) { for(int b=0;b<N;b++)if(DIST[m][b]!=INF)getmin(DIST[a][b],DIST[a][m]+DIST[m][b]); } } static bool inq[100][100]; for(int i=0;i<N;i++)for(int j=0;j<N;j++)inq[i][j]=false; queue<int>qa,qb; DP[0][0]=2,qa.push(0),qb.push(0),inq[0][0]=true; while(!qa.empty()) { const int a=qa.front(),b=qb.front(); qa.pop(),qb.pop(); inq[a][b]=false; if(DIST[a][b]==INF)continue; for(int nxta=0;nxta<N;nxta++)if(DIST[b][nxta]!=INF) { for(int nxtb=0;nxtb<N;nxtb++)if(DIST[nxtb][a]!=INF) { if(nxta==a&&nxtb==b)continue; const int update=DP[a][b]+(DIST[a][b]-1)+DIST[b][nxta]+DIST[nxtb][a]; if(DP[nxta][nxtb]>update) { DP[nxta][nxtb]=update; if(!inq[nxta][nxtb])qa.push(nxta),qb.push(nxtb),inq[nxta][nxtb]=true; } } } } assert(DP[1][1]!=INF); printf("%d\n",DP[1][1]-1); } return 0; }
沒有留言:
張貼留言
歡迎留言或問問題~
若您的留言中包含程式碼,請參考這篇
如果留言不見了請別慌,那是因為被google誤判成垃圾留言,小莫會盡快將其手動還原
注意:只有此網誌的成員可以留言。