1. 程式人生 > 實用技巧 >P3953 逛公園

P3953 逛公園

題目描述

策策同學特別喜歡逛公園。公園可以看成一張N個點M條邊構成的有向圖,且沒有 自環和重邊。其中1號點是公園的入口,N號點是公園的出口,每條邊有一個非負權值, 代表策策經過這條邊所要花的時間。

策策每天都會去逛公園,他總是從1號點進去,從N號點出來。

策策喜歡新鮮的事物,它不希望有兩天逛公園的路線完全一樣,同時策策還是一個 特別熱愛學習的好孩子,它不希望每天在逛公園這件事上花費太多的時間。如果1號點 到N號點的最短路長為d,那麼策策只會喜歡長度不超過d+K的路線。

策策同學想知道總共有多少條滿足條件的路線,你能幫幫它嗎?

為避免輸出過大,答案對P取模。

如果有無窮多條合法的路線,請輸出−1。

輸入格式

第一行包含一個整數 T, 代表資料組數。

接下來T組資料,對於每組資料: 第一行包含四個整數 N,M,K,,每兩個整數之間用一個空格隔開。

接下來M行,每行三個整數\(a_i,b_i,c_i\),代表編號為\(a_i,b_i\)​的點之間有一條權值為 \(c_i\)​的有向邊,每兩個整數之間用一個空格隔開。

輸出格式

輸出檔案包含 T行,每行一個整數代表答案。

輸入輸出樣例

輸入 #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

說明/提示

【樣例解釋1】

對於第一組資料,最短路為 3。 1–5,1–2–4–5,1–2–3–5為 3 條合法路徑。

【測試資料與約定】

對於不同的測試點,我們約定各種引數的規模不會超過如下

這個題,我們可能會想到求一遍最短路,然後直接暴力統計合法的路線數(當然了這特定會TLE啊)。

那我們考慮怎麼優化。

一開始肯定要求一遍最短路,但我們需要的是處理出從n到所有點的路徑。

也就是建反向邊跑最短路(至於為什麼呢,下面會提到的)。

我們首先考慮無解的情況。

當存在一個零環的時候,就說明無解了。

那有解的情況呢?

我們可以對暴力搜尋優化一下,變為記憶化搜尋。

我們用rest表示當前還能比最短路多走多少

如上圖所示,y點為n號點,x為1號點

那麼從x-z-y比從x-y的最短路多走了 dis[y]+e[i].w - dis[x]的距離(這也是我們為什麼要建反邊跑最短路)

那麼我們就可以得出經過每條邊時多走的距離。

接著往下搜就可以了,如果當前多走的距離要比rest大,說明此條方案不行

若到n點之後,rest >0說明我們找到一條合法的路線,這時候直接往回推就可以了

設 f[i][j] 為比 dis[i] 正好多 j 為長度的方案總數

假設有一條從 p 到 now 長度為 w 的一條邊

目標:將 f[now][k] 轉移到 f[p][x] ( k 為比最短路多出的長度)

可以發現只有 x 是不知道的量。

可以得出 x−k=dis[p]−dis[now]+w

再將 k 移過去

x=dis[p]+w-dis[now]+k

dp方程就是

f[p][x]=(f[p][x]+f[now][k]) mod p

記憶化搜尋就完事了

不懂得同學下面有帶註釋的程式碼QAQ

程式碼

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;
const int N = 1e5+10;
int n,m,T,k,p,tot = 0,u,v,w,sum = 0;
int head[N],dis[N],f[N][59],hed[N];
bool vis[N][59],in[N];
struct node{int to,net,w;}e[200010],edge[200010];
priority_queue<pair<int,int>, vector<pair<int,int> >, greater< pair<int,int> > >q;
inline int read()
{
	int s = 0,w = 1; char ch = getchar();
	while(ch < '0' || ch > '9'){if(ch == '-') w = -1; ch = getchar();}
	while(ch >= '0' && ch <= '9'){s = s * 10+ch -'0'; ch = getchar();}
	return s * w;
}
void add(int x,int y,int w)
{
	e[++tot].w = w;
	e[tot].to = y;
	e[tot].net = head[x];
	head[x] = tot;
	edge[++sum].w = w;
	edge[sum].to = x;
	edge[sum].net = hed[y];
	hed[y] = sum;
}
void chushihua()
{
	tot = 0, sum = 0;
	memset(head,0,sizeof(head));
	memset(vis,0,sizeof(vis));
	memset(f,0,sizeof(f));
	memset(hed,0,sizeof(hed));
	memset(dis,0x3f3f,sizeof(dis));
	memset(in,0,sizeof(in));	
}
void dij()//在反向圖上跑最短路
{
	q.push(make_pair(0,n)); dis[n] = 0; 
	while(!q.empty())
	{
		int t = q.top().second; q.pop();
		if(in[t]) continue;
		in[t] = 1;
		for(int i = hed[t]; i; i = edge[i].net)
		{
			int to = edge[i].to;
			if(dis[to] > dis[t] + edge[i].w)
			{
				dis[to] = dis[t] + edge[i].w;
				q.push(make_pair(dis[to],to));
			}
		}
	} 
}
int dfs(int x,int rest)//記憶化搜尋,rest表示當前還能比最短路多走的距離
{
	if(vis[x][rest]) return -1; //判0環 
	if(f[x][rest]) return f[x][rest];//記憶化搜尋
	if(x == n) f[x][rest] = 1;//到達n點,說明找到一條可行的方案數
	vis[x][rest] = 1;//打個標記
	for(int i = head[x]; i; i = e[i].net)
	{
		int to = e[i].to;
		int tmp = dis[to] + e[i].w - dis[x];//計算走這條邊要比走最短路多經過的距離
		if(rest - tmp >= 0)//後面還能再接上 
		{
			int now = dfs(to,rest-tmp);//往下搜
			if(now == -1)
			{
				return f[x][rest] = -1;
			}
			f[x][rest] =(f[x][rest] + now) % p;//f[x][rest]表示從x到n還能比最短路多走rest的方案數
		}
	}
	vis[x][rest] = 0;
	return f[x][rest]%p;
}
int main()
{
	T = read();
	while(T--)
	{
		chushihua();
		n = read(); m = read(); k = read(); p = read();
		for(int i = 1; i <= m; i++)
		{
			u = read(); v = read(); w = read();
			add(u,v,w);//建雙向邊
		}
		dij();
		printf("%d\n",dfs(1,k));
	}
	return 0;
}

ENDING