NOIP 2017 逛公园(最短路)(Tarjan)(记忆化搜索)

传送门
先考虑没有 0 0 0 边的情况
求出 1 到 n n n 的最短路,用 f i , j f_{i,j} fi,j 表示到当前点,比最短路多 j j j 的方案数
f 1 , 0 = 1 f_{1,0}=1 f1,0=1 f u , j = f v , j − ( d i s [ v ] + w [ i ] − d i s [ u ] ) f_{u,j}=f_{v,j-(dis[v]+w[i]-dis[u])} fu,j=fv,j(dis[v]+w[i]dis[u])
因为这并不是一个 d a g dag dag,而是一个存在环的有向图
n n n 开始建反图,记忆化搜索即可
考虑有 0 边的时候有什么影响
就是如果存在一个 0 环或一个 0 - 强连通分量在 1 - n 的一条长度 ≤ d i s + k \le dis+k dis+k 的路径上
为了支持这个判断
我们可以先把 0 边拿出来跑强连通分量,枚举一条两个端点在一个分量里的 0 边,对两个端点分别求出到 1 和到 n 的最短路
于是建返图,跑从 1 和 n 开始的最短路即可
对细节的要求比较高


#include<bits/stdc++.h>
#define cs const
using namespace std;
int read(){
	int cnt = 0, f = 1; char ch = 0;
	while(!isdigit(ch)){ ch = getchar(); if(ch == '-') f = -1;}
	while(isdigit(ch)) cnt = cnt*10 + (ch-'0'), ch = getchar();
	return cnt * f;
}
cs int N = 1e5 + 5, M = 2e5 + 5, K = 52;
int T, n, m, k, p, dp[N][K];
void Add(int &a, int b){ a = a + b >= p ? a + b - p : a + b; }
struct Dijsktra{
	int first[N], nxt[M], to[M], w[M], tot;
	void add(int x, int y, int z){ nxt[++tot] = first[x], first[x] = tot, to[tot] = y, w[tot] = z;}
	int dis[N], st; bool vis[N];
	typedef pair<int, int> pi;
	#define mp make_pair
	void clear(){ memset(first, 0, sizeof(first)); tot = 0; }
	void Dij(){
		memset(dis, 0x3f, sizeof(dis));
		memset(vis, 0, sizeof(vis));
		priority_queue<pi> q; q.push(mp(0, st));
		dis[st] = 0;
		while(!q.empty()){
			int x = q.top().second; q.pop();
			if(vis[x]) continue; vis[x] = true;
			for(int i = first[x]; i; i = nxt[i]){
				int t = to[i]; if(dis[t] > dis[x] + w[i]){
					dis[t] = dis[x] + w[i]; q.push(mp(-dis[t], t));
				}
			}
		} 
	} 
}G[2];
int dfs(int u, int delta){
	if(~dp[u][delta]) return dp[u][delta];
	int ans = 0;
	for(int i = G[1].first[u]; i; i = G[1].nxt[i]){
		int t = G[1].to[i], now = G[1].w[i] - G[0].dis[u] + G[0].dis[t];
		if(now <= delta) Add(ans, dfs(t, delta - now));
	} return dp[u][delta] = ans;
}
int DP(){ 
	memset(dp, -1, sizeof(dp));
	int ans = 0; dp[1][0] = 1;
	for(int i = 0; i <= k; i++) Add(ans, dfs(n, i));
	return ans; 
}
namespace Tarjan{
	int first[N], nxt[M], to[M], tot;
	void add(int x, int y){ nxt[++tot] = first[x], first[x] = tot, to[tot] = y; }
	int dfn[N], low[N], sta[N]; bool insta[N]; int idx[N], ct, sign, top;
	void clear(){ 
		memset(first, 0, sizeof(first)); tot = 0; 
		memset(dfn, 0, sizeof(dfn));
		memset(low, 0, sizeof(low)); top = sign = ct = 0;
	}
	void dfs(int u){
		dfn[u] = low[u] = ++sign;
		sta[++top] = u; insta[u] = true;
		for(int i = first[u]; i; i = nxt[i]){
			int t = to[i]; if(!dfn[t]) dfs(t), low[u] = min(low[u], low[t]);
			else if(insta[t] && dfn[t] < low[u]) low[u] = dfn[t]; 
		}
		if(dfn[u] == low[u]){ ++ct; do{ idx[sta[top]] = ct; insta[sta[top]] = 0; }while(sta[top--]^u); }
	}
	void Solve(){ for(int i = 1; i <= n; i++) if(!dfn[i]) dfs(i); }
	bool ck(){ 
		if(idx[1] == idx[n]) return true;
		for(int i = 1; i <= n; i++){
			for(int e = first[i]; e; e = nxt[e]){
				if(idx[i] ^ idx[to[e]]) continue;
				if(G[0].dis[i] + G[1].dis[to[e]] <= G[0].dis[n] + k) return true;
			}
		} return false;
	}
}
void Solve(){
	n = read(), m = read(), k = read(), p = read();
	G[0].st = 1;
	G[1].st = n;
	for(int i = 1; i <= m; i++){
		int x = read(), y = read(), z = read();
		G[0].add(x, y, z);
		G[1].add(y, x, z);
		if(z == 0) Tarjan::add(x, y);
	} 
	G[0].Dij(); 
	if(G[0].dis[n] > 1e9){ puts("-1"); return; }
	G[1].Dij();
	Tarjan::Solve();
	if(Tarjan::ck()){ puts("-1"); return; }
	cout << DP() << '\n';
}
void Clear(){
	G[0].clear();
	G[1].clear();
	Tarjan::clear();
}
int main(){
	T = read(); 
	while(T--) Solve(), Clear();
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

FSYo

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值