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;
}