luogu3953 逛公园 题解

首先跑最短路,本题并不卡那个死掉的算法。

求出 1 号节点到每个点的最短路 \(d\)

然后考虑计数。

计数可以考虑 DP,但是必须满足无后效性。

设计一个类似于分层图的状态。

\(f(x,k)\) 为 1 号节点到 \(x\) 号节点,距离为 \(d(x)+k\) 的方案数。

考虑转移,假定 \(f(y,k_2)\) 能转移到 \(f(x,k_1)\)

\((x \rightarrow y)\) 边权为 \(z\),则有 \[ d(y)+z+k_2=d(x)+k_1 \] \[ k_2=d(x)-d(y)-z+k_1 \]

转移为 \[ f(x,k)= \sum_{x \rightarrow y} {f(y,d(x)-d(y)+k-z)} \] 对于 \((x \rightarrow y)\),关于 \(x\) 的状态反而依赖关于 \(y\) 的状态。那么必定是建反图,然后记忆化搜索。

边界 \(f(1,0)=1\)

答案 \[ \sum_{0 \le i \le K} f(n,i) \]  

考虑无解的情况。

题目描述中告诉我们有 0 边。

我们发现,有无穷多条合法路径,当且仅当有边权为 0 的环。又因为没有负边权,所以 0 环一定由若干 0 边构成。

如何找出 0 环呢?

观察我们的转移,不难想到,在 0 边上,\(k=d(x)-d(y)+k-z\),即会递归到 \(f(y,k)\)

如果有 0 环,那么一定又会再次访问到 \(f(x,k)\)

所以对于每个 \(f(x,k)\),用 \(v(x,k)\) 来记录访问情况。每访问到一个状态,将其记为 1,访问完之后就改回 0。如果访问到 1 的状态,就证明有 0 环,无解。

这也恰好说明了,这个状态是“无环”的。并不是真的不可能存在环,但是如果访问一圈回到 \(f(x,k)\),说明存在环,也就直接无解了,不需要再考虑别的。

#include<bits/stdc++.h>
using namespace std;
#define PII pair<int,int>
#define mp make_pair
#define SET(x,y) memset(x,y,sizeof(x))
const int N=1e5+10;
int t, n, m, K, P, ans, QwQ, f[N][66], d[N], fa[N];
int cnt, h[N], ver[N<<1], nxt[N<<1], w[N<<1];
int cnt2, h2[N], ver2[N<<1], nxt2[N<<1], w2[N<<1];
bool v[N], vis[N][66];
int r_() {
    int a=0; char c=getchar();
    for(;!isdigit(c);c=getchar());
    for(;isdigit(c);a=(a<<1)+(a<<3)+(c^48),c=getchar());
    return a;
}
void add(int x,int y,int z) { ver[++cnt]=y, w[cnt]=z, nxt[cnt]=h[x], h[x]=cnt; }
void add2(int x,int y,int z) { ver2[++cnt2]=y, w2[cnt2]=z, nxt2[cnt2]=h2[x], h2[x]=cnt2; }
void dijk() {
	priority_queue<PII > q;
	SET(d,0x3f);
	d[1]=0, q.push(mp(0,1));
	while(q.size()) {
		int x=q.top().second; q.pop();
		if(v[x]) continue;
		v[x]=1;
		for(int i=h[x];i;i=nxt[i]) {
			int y=ver[i], z=w[i];
			if(d[y]>d[x]+z) {
				d[y]=d[x]+z;
				q.push(mp(-d[y],y));	
			}
		}
	}
}
int dp(int x,int k) {	
	if(k<0) return 0;
	if(vis[x][k]) { QwQ=1; return 0; }
	if(f[x][k]) return f[x][k];
	int ans=0;
	vis[x][k]=1;
	for(int i=h2[x];i;i=nxt2[i]) {
		int y=ver2[i], z=w2[i];
		(ans+=dp(y,d[x]-d[y]+k-z))%=P;
		if(QwQ) return 0;
	}
	vis[x][k]=0;
	return f[x][k]=ans;
}
int main() {
	for(int t=r_();t--;) {
		n=r_(), m=r_(), K=r_(), P=r_();
		while(m--) {
			int x=r_(), y=r_(), z=r_();
			add(x,y,z), add2(y,x,z);
		}
		dijk();
		dp(1,0);
		f[1][0]=1, ans=0;
		for(int i=0;i<=K;++i) ans=(ans+dp(n,i))%P;
		if(QwQ) puts("-1"); else printf("%d\n",ans);
		QwQ=cnt=cnt2=0;
        SET(v,0), SET(vis,0), SET(f,0), SET(h,0), SET(h2,0);
	}
}

luogu3953 逛公园 题解
https://yozora0908.github.io/2021/lg3953-solution/
作者
yozora0908
发布于
2021年10月2日
许可协议