NOIP2017 逛公园 分层图+拓扑排序

就快把NOIP的题都做完了(事实上剩下的题都是最毒瘤的,比如天天爱跑步
正解暂时还不会写,在这里先贴一个分层图的题解(会被卡30分,Luogu上开O2才能过

分层图:
由于 k &lt; = 50 k&lt;=50 k<=50,把每个点拆成 k + 1 k+1 k+1个点,表示经过该点时,超出最短路长度 j j j的情况。
首先,预处理1到所有点的最短路。
然后连边,如果当前点到另一个点多出来的总距离不超过1到那个点的最短距离+k 就连一条边。
然后拓扑排序,每走一步累加方案数。
有零环在满足条件的路径上的时候:注意到一个点如果是零环的一部分,则该点度数永远不可能为0。
原因很简单,因为一个点只会被小于等于该层数的点更新,所以如果有一个零环位于满足条件的路径上,则这个环上的每一个节点都不会被选到,因为它们的入度始终大于0。而一个非零环呢?因为这个环被分层图拆成了一条链,所以拓扑排序没有问题。
最后判断是否有到n点后度数大于0的点,如果有就存在零环。(如果判断每一个点,则可能会有不在满足路径上的零环被选中。)

#pragma GCC optimize(2)
#include<cstdio>
#include<queue>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN = 100001;
const int MAXM = 200001;

int fir[MAXN], nxt[MAXM], to[MAXM], len[MAXM], cnt;
int dis[MAXN], vis[MAXN], dp[MAXN][51], du[MAXN][51];

struct Node{
	int u, dis;
	bool operator < (const Node &a) const{
		return dis > a.dis;
	}
};
priority_queue <Node> q;

inline char nc(){
	static char buf[100000], *p1 = buf, *p2 = buf;
	return p1==p2&&(p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++;
}
inline int read(){
	int k = 0; char ch = nc();
	while(ch < '0' || ch > '9') ch = nc();
	while(ch >= '0' && ch <= '9'){k = k*10 + ch - '0', ch = nc();}
	return k;
}

inline void add_edge(int a, int b, int l){
	len[cnt] = l;
	to[cnt] = b;
	nxt[cnt] = fir[a];
	fir[a] = cnt++;
}

void Dijkstra(int u){
	memset(vis, false, sizeof(vis));
	memset(dis, 0x3f, sizeof(dis)); dis[u] = 0;
	q.push((Node){u, 0});
	while(!q.empty()){
		u = q.top().u; q.pop();
		if(vis[u]) continue;
		vis[u] = true;
		for(int i = fir[u]; i != -1; i = nxt[i]){
			int v = to[i];
			if(dis[v] > dis[u] + len[i]){
				dis[v] = dis[u] + len[i];
				q.push((Node){v, dis[v]});
			}
		}
	}
}

int main(){
	int T = read();
	while(T--){
		memset(fir, -1, sizeof(fir)); cnt = 0;
		memset(du, 0, sizeof(du));
		memset(dp, 0, sizeof(dp));
		int n = read(), m = read(), k = read(), p = read();
		for(int i = 1; i <= m; i++){
			int a = read(), b = read(), l = read();
			add_edge(a, b, l);
		}
		Dijkstra(1);
		for(int u = 1; u <= n; u++){
			for(int i = fir[u]; i != -1; i = nxt[i]){
				int v = to[i];
				for(int j = 0; j <= k; j++){
					if(dis[u] + len[i] + j - dis[v] <= k){
						du[v][dis[u] + len[i] + j - dis[v]]++;
					}
					else break;
				}
			}
		}
		
		queue < pair<int, int> > q;
		for(int u = 1; u <= n; u++){
			for(int j = 0; j <= k; j++){
				if(!du[u][j]) q.push(make_pair(u, j));
			}
		}
		
		dp[1][0] = 1;
		while(!q.empty()){
			int u = q.front().first, k1 = q.front().second; q.pop();
			for(int i = fir[u]; i != -1; i = nxt[i]){
				int v = to[i], k2 = dis[u] + len[i] + k1 - dis[v];
				if(k2 > k) continue;
				dp[v][k2] += dp[u][k1];
				if(dp[v][k2] >= p) dp[v][k2] -= p;
				if(--du[v][k2] == 0){
					q.push(make_pair(v, k2));
				}
			}
		}
		int Ans = 0, flag = false;
		for(int i = 0; i <= k; i++){
			if(du[n][i] != 0) flag = true;
			Ans = (Ans + dp[n][i]) % p;
		}
		if(flag) puts("-1");
		else printf("%d\n", Ans);
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值