[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 ;
}
posted @ 2018-09-04 18:42  beretty  阅读(227)  评论(0编辑  收藏  举报