1. 程式人生 > >【NOIP2017提高】逛公園(最短路+記憶化搜尋)

【NOIP2017提高】逛公園(最短路+記憶化搜尋)

原題見洛谷。

分析

1,既然需要最短路做基礎,所以需要做一遍最短路演算法。

2,有的點可能到不了N,所以正反各建一個圖,用反向圖跑出dis[i]表示i距離N的最短距離。

3,K最大為50,所以考慮dp的做法。設f[i][k]表示i到N,實際距離-dis[i]<=k 時的方案數,也就是f[i][K]=∑f[i][j],(0<=j<=K)。

4,用記憶化搜尋,分析邊界,f[N][0]=1。

分析狀態轉移方程,如果當前節點為u,比dis[u]大k,現在列舉到一條邊(u,v,w)。

如果u不走最短路,改走v這邊,那麼路會變長:dis[v]+w-dis[u](如果本來就是v這邊最短就等於0),那麼v再往後走時,只需要比dis[v]多出:k-(dis[v]+w-dis[u])即可,利用記憶化搜尋可以快速出解。

5,存在0環的情況。設一個vis陣列,表示(i,k)是否已經入棧,若存在0環,那麼(i,k)還未出棧時又會返回(i,k),這裡只需要一維存i入棧時的k即可。如果存在0環迅速退出輸出-1,。

6,常數優化。除了上述的優化到一維,記得輸入優化,用memset而不是for,邊陣列可以不必清空。

7,注意細節,比如程式碼中dfs裡,if(f[i][k_now]>=0)一句不加“=”會導致超時。

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int MAXN=100005;
const int MAXM=200005;
int N,M,T,P,K,np,np2,flag,last[MAXN],last2[MAXN],dis[MAXN],in_ring[MAXN],vis[MAXN];
LL f[MAXN][51];
struct edge{int to,w,pre;};
edge E[MAXM],E2[MAXM];

char c;
void scan(int &x)
{
	for(c=getchar();c<'0'||c>'9';c=getchar());
	for(x=0;c>='0'&&c<='9';c=getchar()) x=x*10+c-'0';
}

void init()
{
	np=0;np2=0;flag=0;
	memset(last,0,sizeof(last));
	memset(last2,0,sizeof(last2));
	memset(dis,0x3f,sizeof(dis));
	memset(f,-1,sizeof(f));
	memset(vis,0,sizeof(vis));
	memset(in_ring,-1,sizeof(in_ring));
}

void addedge(int u,int v,int w)
{
	E[++np]=(edge){v,w,last[u]};
	last[u]=np;
}

void addedge2(int u,int v,int w)
{
	E2[++np2]=(edge){v,w,last2[u]};
	last2[u]=np2;
}

void SPFA()
{
	queue<int>q; dis[N]=0;
	q.push(N);
	while(!q.empty())
	{
		int i=q.front(); q.pop();
		vis[i]=0;
		for(int p=last2[i];p;p=E2[p].pre)
		{
			int j=E2[p].to;
			if(dis[j]>dis[i]+E2[p].w)
			{
				dis[j]=dis[i]+E2[p].w;
				if(!vis[j])
				{
					vis[j]=1;
					q.push(j);
				}
			}
		}
	}
}

int dfs(int i,int k_now)
{
	if(in_ring[i]==k_now) //出現0環 
	{
		flag=1;
		return 0;
	}
	if(f[i][k_now]>=0) return f[i][k_now]; //記憶化 
	in_ring[i]=k_now; //進入當前環中 
	int sum=0;
	for(int p=last[i];p;p=E[p].pre)
	{
		int j=E[p].to,t=k_now-(dis[j]+E[p].w-dis[i]);  //下一步的差值 
		if(t<0||t>K) continue; //越界 
		sum=(sum+dfs(j,t))%P;
		if(flag) return 0; //如果出現了0環 
	}
	in_ring[i]=-1; //退出環 
	if(i==N&&k_now==0) sum=1; //邊界 
	return f[i][k_now]=sum;
}

void solve()
{
	int u,v,w,i; init();
	scan(N);scan(M);scan(K);scan(P);
	for(i=1;i<=M;i++)
	{
		scan(u);scan(v);scan(w);
		addedge(u,v,w);
		addedge2(v,u,w);
	}
	SPFA();
	int ans=0;
	for(i=0;i<=K;i++)
	{
		ans=(ans+dfs(1,i))%P;
		if(flag) break;
	}
	if(flag) cout<<-1<<'\n';
	else cout<<ans<<'\n';
}

int main()
{
	scan(T);
	while(T--) solve();
	return 0;
}