1. 程式人生 > >[NOIP2017]逛公園 最短路圖 拓撲序DP

[NOIP2017]逛公園 最短路圖 拓撲序DP

none img ble next lose spf 沒有 gist pop

~~~題面~~~

題解:

  挺好的一道題。

  首先我們將所有邊反向,跑出n到每個點的最短路,然後f[i][j]表示從i號節點出發,路徑長比最短路大j的方案數。

  觀察到,如果圖中出現了0環,那麽我們可以通過在環上走無數次來獲得無數條不同路徑,因此這就無解了。

  如果沒有0環,當且僅當這張圖的最短路圖是一個DAG(可以畫圖思考一下),因為如果沒有0環,而最短路圖中出現了環,那麽意味著你可以無數次以最短路到達同一個點,而不增加路徑長,這顯然是不可能的,同理,如果有0環,那麽最短路圖中就會出現環。

  因此我們判斷不合法只需要對圖進行一遍拓撲排序,如果不能將所有點都加入隊列的話,就是出現了環,那麽就輸出-1.

  否則的話我們就按照拓撲序DP。

  從感性的角度上來說,,,我們需要先獲取離終點近的DP值才能更新裏終點遠的DP值。所以要按拓撲序DP(具體證明之類的我也不會)

  DP的時候要先枚舉比最短路長多少,因為DP時要通過原圖轉移,所以一個離終點近的點也可能會利用到離終點遠的點,而DP的轉移顯然要依賴於用來更新其他點的值要 在被需要之前 更新完。

  所以先枚舉點是不對的,因為這樣沒有明確的需要與被需要關系,也可以認為是在一個環上互相轉移了。

  但是觀察比最短路長多少這個條件,它是有明確的需要與被需要關系的,對於f[i][j]而言,j大的要利用j小的轉移,j小的不可能用j大的轉移,因為沒有負邊,所以這就避免了“環”的出現,於是可以保證DP轉移合法。

技術分享圖片
  1 #include<bits/stdc++.h>
  2 using namespace std;
  3 #define R register int
  4 #define AC 100100
  5 #define ac 202000
  6 #define LL long long
  7 
  8 int n, m, k, p, T;
  9 int in[AC], dis[AC];
 10 int q1[ac], head, tail;
 11 LL f[AC][60];
 12 bool z[AC];
 13 
 14 struct node{
15 int dis, x; 16 }; 17 18 struct cmp{ 19 bool operator() (node a, node b){ 20 return a.dis > b.dis; 21 } 22 }; 23 24 priority_queue<node, vector<node>, cmp> q; 25 26 struct road{ 27 int Head[AC], date[ac], Next[ac], len[ac], tot; 28 29 inline void init() 30 { 31 memset(Head, 0, sizeof(Head)); 32 tot = 0; 33 } 34 35 inline void add(int f, int w, int S){ 36 date[++tot] = w, Next[tot] = Head[f], Head[f] = tot, len[tot] = S; 37 } 38 }E1, E2, E3; 39 40 int read() 41 { 42 int x = 0;char c = getchar(); 43 while(c > 9 || c < 0) c = getchar(); 44 while(c >= 0 && c <= 9) x = x * 10 + c - 0, c = getchar(); 45 return x; 46 } 47 48 inline void up(LL &a, LL b) 49 { 50 a += b; 51 if(a > p) a -= p; 52 } 53 54 void pre() 55 { 56 int a, b, c; 57 E1.init(), E2.init(), E3.init(); 58 memset(f, 0, sizeof(f)); 59 // memset(in, 0, sizeof(in)); 60 memset(z, 0, sizeof(z)); 61 head = tail = 0; 62 n = read(), m = read(), k = read(), p = read(); 63 for(R i = 1; i <= m; i ++) 64 { 65 a = read(), b = read(), c = read(); 66 E1.add(b, a, c), E3.add(a, b, c); 67 } 68 memset(dis, 127, sizeof(dis)); 69 dis[n] = 0, q.push((node){0, n}); 70 } 71 72 void spfa() 73 { 74 int x, now; 75 while(!q.empty()) 76 { 77 x = q.top().x, q.pop(); 78 while(z[x] && !q.empty()) x = q.top().x, q.pop(); 79 if(z[x]) break; 80 z[x] = true; 81 for(R i = E1.Head[x]; i; i = E1.Next[i]) 82 { 83 now = E1.date[i]; 84 if(dis[now] > dis[x] + E1.len[i]) 85 { 86 dis[now] = dis[x] + E1.len[i]; 87 q.push((node){dis[now], now}); 88 } 89 } 90 } 91 } 92 93 void tsort() 94 { 95 int x, now; 96 while(head < tail) 97 { 98 x = q1[++head]; 99 for(R i = E2.Head[x]; i; i = E2.Next[i]) 100 { 101 now = E2.date[i], --in[now]; 102 if(!in[now]) q1[++tail] = now; 103 } 104 } 105 } 106 107 void build() 108 { 109 int now; 110 for(R i = 1; i <= n; i ++) 111 { 112 for(R j = E1.Head[i]; j; j = E1.Next[j]) 113 { 114 now = E1.date[j]; 115 if(dis[now] == dis[i] + E1.len[j]) 116 E2.add(i, now, E1.len[j]), ++ in[now]; 117 } 118 //f[i][0] = 1; 119 } 120 f[n][0] = 1; 121 for(R i = 1; i <= n; i ++) 122 if(!in[i]) q1[++tail] = i; 123 tsort(); 124 } 125 126 void getans() 127 { 128 if(tail < n) 129 { 130 memset(in, 0, sizeof(in)); 131 printf("-1\n"); 132 return ; 133 } 134 int now, x; 135 for(R i = 0; i <= k; i ++) 136 { 137 for(R j = 1; j <= n; j ++) 138 { 139 x = q1[j]; 140 for(R l = E3.Head[x]; l; l = E3.Next[l]) 141 { 142 now = E3.date[l]; 143 int t = E3.len[l] - dis[x] + dis[now];//獲取現在新增的路徑長度 144 if(i - t >= 0) up(f[x][i], f[now][i - t]); 145 } 146 } 147 } 148 LL ans = 0; 149 for(R i = 0; i <= k; i ++) up(ans, f[1][i]); 150 printf("%lld\n", ans); 151 } 152 153 void work() 154 { 155 T = read(); 156 while(T --) 157 { 158 pre(); 159 spfa(); 160 build(); 161 getans(); 162 } 163 } 164 165 int main() 166 { 167 freopen("in.in", "r", stdin); 168 work(); 169 fclose(stdin); 170 return 0; 171 }
View Code

[NOIP2017]逛公園 最短路圖 拓撲序DP