[NOIP2017]逛公园
输入输出样例
输入样例#1:
2
5 7 2 10
1 2 1
2 4 0
4 5 2
2 3 2
3 4 1
3 5 2
1 5 3
2 2 0 10
1 2 0
2 1 0
输出样例#1:
3
-1
联赛题好难啊
但是看到\(k<=50\)是不是就能想到把k压入状态?
用\(f[i][j]\)表示到i点已经比到i点的最短路远了j的路径条数
好像记搜比较好写,但是我写了个DP
就是类似于分层图的思想
先枚举j,因为j只有50层,所以可以分层来转移
如果不分层的话可能用来更新ta的状态可能后来还有变化
然后再按照dis的顺序呢更新
因为如果不按照dis的顺序
用来更新ta的状态后来还有变化
会造成答案较小
这样就可以得到70分辣
然后我们考虑一下带有0边时应该怎么处理
首先如果形成0环并且有一个0环上的点在一条合法的路径上(路径长度\(<_{min} dis_{1~n} + k\))
就有无数条路径(因为ta可以在这条路径上一直转圈)
然后如果只是有0边但没有形成0环
还是按照我们刚才的顺序更新答案的话会出现问题
因为dis会出现重复的情况
但是我们在dis相同时应该先更新非0边上的点
因为拓扑序大的一定是由拓扑序小的走过来的,如果不按照拓扑序更新仍然会导致用来更新的状态后来会有变化
所以我们可以用拓扑排序来实现
拓扑排序只连0边方便处理
这样我们就按照dis为第一关键字,拓扑序为第二关键字为顺序更新就可以辣
然后就是我懒得写dijkstra就写了那个死掉的算法
#include<queue>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
const int M = 100005 ;
const int N = 55 ;
using namespace std ;
inline int read() {
char c = getchar() ; int x = 0 , w = 1 ;
while(c>'9'||c<'0') { if(c=='-') w = - 1 ; c = getchar() ; }
while(c>='0' && c<='9') { x = x*10+c-'0' ; c = getchar() ; }
return x*w ;
}
struct E {
int Nxt , to , Dis ;
} edge [M<<1] , rev [M<<1] ;
int hea [M] , rhea[M] , num , rnum ;
inline void add_edge(int from , int to , int dis) {
edge[++ num].Nxt = hea[from] ; edge[num].to = to ;
edge[num].Dis = dis ; hea[from] = num ;
rev[++ rnum].Nxt = rhea[to] ; rev[num].to = from ;
rev[num].Dis = dis ; rhea[from] = rnum ;
}
int n , m , k , p , Ans ;
int f[M][N] , Dist[M] , Disf[M] , c[M] ;
bool exist[M] , vis[M] ;
struct Node { int Id , Dis , dep ; } d[M] ;
inline bool operator < (Node a , Node b) { return a.Dis == b.Dis ? a.dep < b.dep : a.Dis < b.Dis ; }
inline void Tspfa(int T) {
memset(exist , false , sizeof(exist)) ;
memset(Dist , 63 , sizeof(Dist)) ;
queue < int > q ;
Dist[T] = 0 ; q.push(T) ;
while(!q.empty()) {
int u = q.front() ; q.pop() ; exist[u] = false ;
for(int i = rhea[u] ; i ; i = rev[i].Nxt) {
int v = rev[i].to ;
if(Dist[v] > Dist[u] + rev[i].Dis) {
Dist[v] = Dist[u] + rev[i].Dis ;
if(!exist[v]) q.push(v) , exist[v] = true ;
}
}
}
}
inline void Sspfa(int S) {
memset(exist , false , sizeof(exist)) ;
memset(Disf , 63 , sizeof(Disf)) ;
queue< int > q ;
Disf[S] = 0 ; q.push(S) ;
while(!q.empty()) {
int u = q.front() ; q.pop() ; exist[u] = false ;
for(int i = hea[u] ; i ; i = edge[i].Nxt) {
int v = edge[i].to ;
if(Disf[v] > Disf[u] + edge[i].Dis) {
Disf[v] = Disf[u] + edge[i].Dis ;
if(!exist[v]) q.push(v) , exist[v] = true ;
}
}
}
}
struct EDDE { int Nxt , to ; }Edge[M];
int Hea[M] , Num ;
inline void Insert(int from , int to) {Edge[++Num].Nxt = Hea[from] ; Edge[Num].to = to ; Hea[from] = Num ; }
inline bool Check() {
queue < int > q ; int tot = 0 ;
for(int i = 1 ; i <= n ; i ++)
if(c[i] == 0) q.push(i) , vis[i] = true , -- c[i] , d[i].dep = ++tot ; ;
while(!q.empty()) {
int u = q.front() ; q.pop() ;
for(int i = Hea[u] ; i ; i = Edge[i].Nxt) {
int v = Edge[i].to ; --c[v] ;
if(c[v] == 0) { vis[v] = true ; q.push(v) ; d[v].dep = ++tot ; }
}
}
for(int i = 1 ; i <= n ; i ++)
if(!vis[i])
if(Dist[i] + Disf[i] <= Disf[n] + k)
return false ;
return true ;
}
inline void Clear() {
Ans = 0 ; memset(f , 0 , sizeof(f)) ; memset(c , 0 , sizeof(c)) ;
memset(vis , 0 , sizeof(vis)) ; memset(hea , 0 , sizeof(hea)) ; num = 0 ;
memset(rhea , 0 , sizeof(rhea)) ; rnum = 0 ;
memset(d , 0 , sizeof(d)) ; memset(Hea , 0 , sizeof(Hea)) ; Num = 0 ;
}
int main() {
int T = read() ;
while(T -- ) {
Clear() ;
n = read() ; m = read() ; k = read() ; p = read() ;
for(int i = 1 , u , v , w ; i <= m ; i ++) {
u = read() , v = read() , w = read() ; add_edge(u , v , w) ;
if(w == 0) ++c[v] , Insert(u , v) ;
}
Tspfa(n) ; Sspfa(1) ;
if(!Check()) { printf("-1\n") ; continue ; }
for(int i = 1 ; i <= n ; i ++) d[i].Id = i , d[i].Dis = Disf[i] ;
sort(d + 1 , d + n + 1) ;
f[1][0] = 1 ;
for(int j = 0 ; j <= k ; j ++)
for(int i = 1 ; i <= n ; i ++) {
int u = d[i].Id , dis = d[i].Dis ;
for(int l = hea[u] ; l ; l = edge[l].Nxt) {
int v = edge[l].to ;
if(Disf[u] + j + edge[l].Dis - Disf[v] > k) continue ;
f[v][Disf[u] + j + edge[l].Dis - Disf[v]] = ( f[v][Disf[u] + j + edge[l].Dis - Disf[v]] + f[u][j] )%p ;
}
}
for(int i = 0 ; i <= k ; i ++) Ans = (Ans + f[n][i]) % p ;
printf("%d\n",Ans) ;
}
return 0 ;
}
upd : 又去写抄了一发记搜,感觉记搜比DP好写多了
就是设\(f[i][j]\)表示到点i时与从1到i的最短路径距离差<=j时的路径数
然后就是直接用 \(revdis[v] - revdis[u] + edge[i].dis\)表示此次的路径偏差
然后如何判断0环呢?
如果在更新这种状态的时候又碰到相同的状态
那么就一定是0环了
记搜真好写
#include<queue>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
const int M = 100005 ;
const int N = 55 ;
using namespace std ;
inline int read() {
char c = getchar() ; int x = 0 , w = 1 ; while(c>'9'||c<'0') { if(c=='-') w = -1 ; c = getchar() ; }
while(c>='0'&&c<='9') { x = x*10+c-'0' ; c = getchar() ; } return x*w ;
}
int n , m , k , p ;
int rhea[M] , hea[M] , rnum , num , Dist[M] , f[M][N] , Ans ;
bool exist[M] , vis[M][N] ;
struct E { int Nxt , to , Dis ; } rev[M<<1] , edge[M<<1] ;
struct Node { int Id , Dis ; };
inline bool operator < (Node a , Node b) { return a.Dis > b.Dis ; }
inline void add_edge(int from , int to , int dis) {
edge[++num].Nxt = hea[from] ; edge[num].to = to ; edge[num].Dis = dis ; hea[from] = num ;
rev[++rnum].Nxt = rhea[to] ; rev[rnum].to = from ; rev[rnum].Dis = dis ; rhea[to] = rnum ;
}
inline void DijkstraT(int T) {
priority_queue< Node > q ; q.push((Node){T , 0}) ;
memset(exist , false , sizeof(exist)) ; Dist[T] = 0 ;
while(!q.empty()) {
int u = q.top().Id ; q.pop() ; if(exist[u]) continue ; exist[u] = true ;
for(int i = rhea[u] , v ; i ; i = rev[i].Nxt) {
v = rev[i].to ;
if(Dist[v] > Dist[u] + rev[i].Dis) Dist[v] = Dist[u] + rev[i].Dis , q.push((Node){ v , Dist[v] }) ;
}
}
}
int Dfs(int u , int res) {
if(vis[u][res]) return - 1 ; if(f[u][res]) return f[u][res] ;
vis[u][res] = true ; if(u == n) f[u][res] = 1 ;
for(int i = hea[u] , v , w , c ; i ; i = edge[i].Nxt) {
v = edge[i].to ; c = Dist[v] - Dist[u] + edge[i].Dis ; if(c > res) continue ;
w = Dfs(v , res - c) ; if(w == -1) return f[u][res] = -1 ; f[u][res] = (f[u][res] + w)%p ;
}
vis[u][res] = false ; return f[u][res] ;
}
inline void Clear() {
memset(hea , 0 , sizeof(hea)) ; num = 0 ; memset(rhea , 0 , sizeof(rhea)) ; rnum = 0 ;
memset(f , 0 , sizeof(f)) ; memset(Dist , 63 , sizeof(Dist)) ; memset(vis , 0 , sizeof(vis)) ;
}
int main() {
int T = read() ;
while(T -- ) {
Clear() ; n = read() ; m = read() ; k = read() ; p = read() ;
for(int i = 1 , u , v , w ; i <= m ; i ++) { u = read() , v = read() , w = read() ; add_edge(u , v , w) ; }
DijkstraT(n) ;
cout << Dfs(1 , k) << endl ;
}
return 0 ;
}