【NOIP2017】逛公园【最短路DAG】【dp】【拓扑排序】

题意:给一张帯权有向图,求 1 1 1 n n n 长度不超过最短路长度 + k +k +k 的路径条数 模 P P P。有无数条输出 − 1 -1 1

n ≤ 1 0 5 , m ≤ 2 × 1 0 5 , k ≤ 50 n\leq 10^5,m\leq 2\times 10^5,k\leq 50 n105,m2×105,k50,边权非负

先往最短路图想

发现 k k k 很小,考虑硬上 dp。

f ( u , k ) f(u,k) f(u,k) 表示从 u u u n n n 多走 k k k 的路径数。

转移枚举下一步往哪里走。

f ( u , k ) = ∑ ( u , v ) ∈ E f ( v , k − ( w ( u , v ) + d i s u − d i s v ) ) f(u,k)=\sum_{(u,v)\in E}f(v,k-(w(u,v)+dis_u-dis_v)) f(u,k)=(u,v)Ef(v,k(w(u,v)+disudisv))

其中 d i s u dis_u disu 1 1 1 u u u 最短路长度。

在没有零边的基础上,右边这块 w ( u , v ) + d i s u − d i s v w(u,v)+dis_u-dis_v w(u,v)+disudisv 0 0 0 当且仅当 ( u , v ) (u,v) (u,v) 在最短路图上。因为这是个 DAG,所以在最短路图上跑拓扑排序,然后枚举 k k k 更新就可以了。这样可以拿到 70 70 70 分的好成绩。

有零边时可能会出现 d i s n + k dis_n+k disn+k 内可达的零环,这样答案为无穷大。如果拓扑排序后点 i i i 入度不为 0 0 0 说明在零环上,此时如果 d i s i + d i s i ′ ≤ d i s n + k dis_i+dis'_i \leq dis_n+k disi+disidisn+k 就输出 − 1 -1 1。( d i s ′ dis' dis 为到 n n n 的最短距离)

否则的话反正都走不到这里,直接把伪的拓扑序继续往后做。

复杂度 O ( n log ⁡ n + n k ) O(n\log n+nk) O(nlogn+nk)

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cctype>
#include <algorithm>
#include <utility>
#include <queue>
#define MAXN 100005
#define MAXM 200005
using namespace std;
int n,m,k,P;
inline int add(const int& x,const int& y){return x+y>=P? x+y-P:x+y;}
inline int read()
{
	int ans=0;
	char c=getchar();
	while (!isdigit(c)) c=getchar();
	while (isdigit(c)) ans=(ans<<3)+(ans<<1)+(c^48),c=getchar();
	return ans;
}
struct edge{int u,v,w;}e[MAXM];
int head[MAXN],nxt[MAXM],cnt;
inline void addnode(int u,int v,int w)
{
	e[++cnt]=(edge){u,v,w};
	nxt[cnt]=head[u];
	head[u]=cnt;
}
template<typename T,typename cmp=less<T> >
struct heap
{
	T val[MAXM];
	T* end;
	heap(){end=val;}
	inline bool empty(){return end==val;}
	inline void push(const T& v){*(end++)=v;push_heap(val,end,cmp());}
	inline T pop(){pop_heap(val,end,cmp());return *(--end);}	
};
typedef pair<int,int> pi;
heap<pi,greater<pi> > q;
int dis[MAXN],disn[MAXN],deg[MAXN];
inline void dij(int* dis,int s=1)
{
	memset(dis,0x3f,sizeof(disn));
	dis[s]=0;
	q.push(make_pair(0,s));
	while (!q.empty())
	{
		int u=q.pop().second;
		for (int i=head[u];i;i=nxt[i])
			if (dis[u]+e[i].w<dis[e[i].v])
				dis[e[i].v]=dis[u]+e[i].w,q.push(make_pair(dis[e[i].v],e[i].v));
	}
}
int lis[MAXN],vis[MAXN],tim;
void dfs(int u)
{
	vis[u]=1;
	for (int i=head[u];i;i=nxt[i])
		if (dis[u]+e[i].w==dis[e[i].v])
		{
			++deg[e[i].v];
			if (!vis[e[i].v]) dfs(e[i].v);
		}
}
inline void topsort()
{
	queue<int> q;
	tim=0;
	q.push(1);
	while (!q.empty())
	{
		int u=q.front();q.pop();
		lis[++tim]=u;
		for (int i=head[u];i;i=nxt[i])
			if (dis[u]+e[i].w==dis[e[i].v]&&(--deg[e[i].v]==0))
				q.push(e[i].v);
	}
}
int f[55][MAXN],u[MAXM],v[MAXM],w[MAXM];
int main()
{
	for (int T=read();T;T--)
	{
		cnt=tim=0;
		memset(head,0,sizeof(head));
		memset(nxt,0,sizeof(nxt));
		memset(deg,0,sizeof(deg));
		memset(vis,0,sizeof(vis));
		n=read(),m=read(),k=read(),P=read();
		for (int i=1;i<=m;i++) u[i]=read(),v[i]=read(),w[i]=read(),addnode(v[i],u[i],w[i]);
		dij(disn,n);
		cnt=0;
		memset(head,0,sizeof(head));
		memset(nxt,0,sizeof(nxt));		
		for (int i=1;i<=m;i++) addnode(u[i],v[i],w[i]);
		dij(dis);
		dfs(1);
		topsort();
		int ans=0;
		for (int i=1;i<=n;i++) if (deg[i]&&dis[i]+disn[i]<=dis[n]+k) {puts("-1");goto end;}
		memset(f,0,sizeof(f));
		f[0][n]=1;
		for (int t=0;t<=k;t++)
			for (int x=tim;x>=1;x--)
			{
				int u=lis[x];
				for (int i=head[u];i;i=nxt[i])
				{
					int del=e[i].w-dis[e[i].v]+dis[u];
					if (t>=del) f[t][u]=add(f[t][u],f[t-del][e[i].v]);
				}
			}
		for (int i=0;i<=k;i++) ans=add(ans,f[i][1]);
		printf("%d\n",ans);
		end:;
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值