yet another NOIP2017(
一年半以前写的做法被叉掉了/ll
洛谷题目页面传送门
给定一张有向图(G=(V,E),|V|=n,|E|=m),不包含重边与自环。求(1 o n)的长度不超过最短路加上(s)的路径个数,对给定模数取模。若有无限条,输出(-1)。本题多测。
(ninleft[1,10^5 ight],minleft[1,2 imes10^5 ight],sin[1,50])。边权非负。测试组数不超过(5)。
先不考虑有边为(0)的情况。这种情况不难想到一个不是很难的DP。
先Dijkstra算出(1 o i)的最短路径(dis_i)。那么不难发现一个很好的结论:在长度不超过(dis_n+s)的任意一条(1 o n)路上的任意一个点(x)处,都满足(1 o x)的长度不超过(dis_x+s)。因为如果超过了,即使(x o n)取最短路也不可能救得回来。
数据范围提示我们std大概是(mathrm O(ns))(认为(n,m)同阶)的,则考虑设(dp_{i,j}(jin[0,s]))表示长度为(dis_i+j)的(1 o i)路径个数。边界:(dp_{1,0}=1),目标:(sumlimits_{i=0}^sdp_{n,i}),转移方程:
至于DP顺序?反正无后效性就是了,你闲着无聊可以把所有状态拓扑排序一下,我用我的记忆化搜索(
再考虑有边为(0)的情况,这时我们要考虑判无限条。一年半以前的naive想法是:有无限条当且仅当有(0)环。然后居然AC了?这里吐槽一下NOIP的数据质量。直到现在——有吏夜叉人。反例很简单,只需要构造出一个(0)环使得没有合法路径经过这个环上任意一个点即可。
解决方案也不难。考虑对于每个环上的节点,都看是否有合法路径经过它即可。如何算是否有合法路径经过它?只需要看经过它的最短(1 o n)路径长度是否不超过(dis_n+s)。如何算经过它的最短(1 o n)路径长度?只需要结合之前Dijkstra求出来的(dis)数组,再Dijkstra算出从(n)出发经过反向边到达每个点的最短路径即可。如何算每个节点是否在环上?Tarjan求SCC即可,所在环大小超过(1)就在环上(因为无自环),DFS和拓扑排序都不再适用,因为它们只能判是否有环。
时间复杂度(mathrm O!left(sum(nlog n+ns ) ight))。常数很大,但居然到了(2mathrm 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
",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<<"
";
}
int main(){
int testnum;
cin>>testnum;
while(testnum--)mian();
return 0;
}