洛谷 P3953 - 逛公园

yet another NOIP2017(

一年半以前写的做法被叉掉了/ll

洛谷题目页面传送门

给定一张有向图\(G=(V,E),|V|=n,|E|=m\),不包含重边与自环。求\(1\to n\)的长度不超过最短路加上\(s\)的路径个数,对给定模数取模。若有无限条,输出\(-1\)。本题多测。

\(n\in\left[1,10^5\right],m\in\left[1,2\times10^5\right],s\in[1,50]\)。边权非负。测试组数不超过\(5\)

先不考虑有边为\(0\)的情况。这种情况不难想到一个不是很难的DP。

先Dijkstra算出\(1\to i\)的最短路径\(dis_i\)。那么不难发现一个很好的结论:在长度不超过\(dis_n+s\)的任意一条\(1\to n\)路上的任意一个点\(x\)处,都满足\(1\to x\)的长度不超过\(dis_x+s\)。因为如果超过了,即使\(x\to n\)取最短路也不可能救得回来。

数据范围提示我们std大概是\(\mathrm O(ns)\)(认为\(n,m\)同阶)的,则考虑设\(dp_{i,j}(j\in[0,s])\)表示长度为\(dis_i+j\)\(1\to i\)路径个数。边界:\(dp_{1,0}=1\),目标:\(\sum\limits_{i=0}^sdp_{n,i}\),转移方程:

\[dp_{i,j}=\sum_{(k,j,len)\in E}[dis_i+j-len\in[dis_k,dis_k+s]]dp_{k,dis_i+j-len-dis_k} \]

至于DP顺序?反正无后效性就是了,你闲着无聊可以把所有状态拓扑排序一下,我用我的记忆化搜索(

再考虑有边为\(0\)的情况,这时我们要考虑判无限条。一年半以前的naive想法是:有无限条当且仅当有\(0\)环。然后居然AC了?这里吐槽一下NOIP的数据质量。直到现在——有吏夜叉人。反例很简单,只需要构造出一个\(0\)环使得没有合法路径经过这个环上任意一个点即可。

解决方案也不难。考虑对于每个环上的节点,都看是否有合法路径经过它即可。如何算是否有合法路径经过它?只需要看经过它的最短\(1\to n\)路径长度是否不超过\(dis_n+s\)。如何算经过它的最短\(1\to n\)路径长度?只需要结合之前Dijkstra求出来的\(dis\)数组,再Dijkstra算出从\(n\)出发经过反向边到达每个点的最短路径即可。如何算每个节点是否在环上?Tarjan求SCC即可,所在环大小超过\(1\)就在环上(因为无自环),DFS和拓扑排序都不再适用,因为它们只能判是否有环。

时间复杂度\(\mathrm O\!\left(\sum(n\log n+ns )\right)\)。常数很大,但居然到了\(2\mathrm s\)?这是我所没想到的(果然人傻常数大)。

代码:

#include<bits/stdc++.h>
using namespace std;
#define mp make_pair
#define X first
#define Y second
#define pb push_back
const int N=100000,S=50;
int n,m,s,mod;
vector<pair<int,int> > nei[N+1],rnei[N+1];
bool vis[N+1];
int dis[N+1],dis0[N+1];
void dijkstra(){//正图最短路 
	priority_queue<pair<int,int>,vector<pair<int,int> >,greater<pair<int,int> > > q;
	memset(dis,0x3f,sizeof(dis));memset(vis,0,sizeof(vis));
	q.push(mp(dis[1]=0,1));
	while(q.size()){
		int x=q.top().Y;
		q.pop();
		if(vis[x])continue;
		vis[x]=true;
		for(int i=0;i<nei[x].size();i++){
			int y=nei[x][i].X,len=nei[x][i].Y;
			if(dis[x]+len<dis[y])q.push(mp(dis[y]=dis[x]+len,y));
		}
	}
//	cout<<"dis=";for(int i=1;i<=n;i++)cout<<dis[i]<<" ";puts("");
}
void dijkstra0(){//反图最短路 
	priority_queue<pair<int,int>,vector<pair<int,int> >,greater<pair<int,int> > > q;
	memset(dis0,0x3f,sizeof(dis0));memset(vis,0,sizeof(vis));
	q.push(mp(dis0[n]=0,n));
	while(q.size()){
		int x=q.top().Y;
		q.pop();
		if(vis[x])continue;
		vis[x]=true;
		for(int i=0;i<rnei[x].size();i++){
			int y=rnei[x][i].X,len=rnei[x][i].Y;
			if(dis0[x]+len<dis0[y])q.push(mp(dis0[y]=dis0[x]+len,y));
		}
	}
}
int dfn[N+1],low[N+1],nowdfn;
int stk[N],top;
bool ins[N+1];
vector<vector<int> > scc;
void dfs_tar(int x){
	dfn[x]=low[x]=++nowdfn;
	ins[stk[top++]=x]=true;
	for(int i=0;i<nei[x].size();i++){
		int y=nei[x][i].X,len=nei[x][i].Y;
		if(len)continue;
		if(!dfn[y])dfs_tar(y),low[x]=min(low[x],low[y]);
		else if(ins[y])low[x]=min(low[x],dfn[y]);
	}
	if(dfn[x]==low[x]){
		scc.pb(vector<int>());
		while(true){
			int y=stk[--top];
			ins[y]=false;
			scc.back().pb(y);
			if(y==x)break;
		}
	}
}
int dp[N+1][S+1];
int dfs(int x,int y){//记忆化搜索 
	if(x==1&&y==0)return 1;//边界 
	int &res=dp[x][y];
	if(~res)return res;
	res=0;
	for(int i=0;i<rnei[x].size();i++){
		int z=rnei[x][i].X,len=rnei[x][i].Y;
		if(dis[z]<=dis[x]+y-len&&dis[x]+y-len<=dis[z]+s)(res+=dfs(z,dis[x]+y-len-dis[z]))%=mod;//转移 
	}
//	printf("dp[%d][%d]=%d\n",x,y,res);
	return res;
}
void mian(){
	cin>>n>>m>>s>>mod;
	for(int i=1;i<=n;i++)nei[i].clear(),rnei[i].clear();
	while(m--){
		int x,y,z;
		scanf("%d%d%d",&x,&y,&z);
		nei[x].pb(mp(y,z));rnei[y].pb(mp(x,z));
	}
	dijkstra();dijkstra0();
	memset(dfn,0,sizeof(dfn));memset(low,0,sizeof(low));nowdfn=0;top=0;memset(ins,0,sizeof(ins));scc.clear();
	for(int i=1;i<=n;i++)if(!dfn[i])dfs_tar(i);//Tarjan求SCC 
	for(int i=0;i<scc.size();i++){
		vector<int> &v=scc[i];
		if(v.size()==1)continue;
		for(int j=0;j<v.size();j++)if(dis[v[j]]+dis0[v[j]]<=dis[n]+s)return puts("-1"),void();//判无限 
	}
	memset(dp,-1,sizeof(dp));
	int ans=0;
	for(int i=0;i<=s;i++)(ans+=dfs(n,i))%=mod;//目标 
	cout<<ans<<"\n";
}
int main(){
	int testnum;
	cin>>testnum;
	while(testnum--)mian();
	return 0;
}
posted @ 2020-08-01 23:36  ycx060617  阅读(149)  评论(4编辑  收藏  举报