洛谷 P3953 [NOIP2017]逛公园

点我前往公园

简析

看到题第一反应用最短路计数的做法骗分(2333333)

总之我们来看看部分分的做法。

(30pts)

(K = 0) ,可以狂喜了。

由于 (spfa) 比较好写,所以在这里仅仅给出需要在模板上进行修改的地方,其他细节请读者自行思考:

if(u == n) continue;
......

if(d[v] == d[u] + w){
	f[v] += f[u];
}else if(d[v] > d[u] + w){
	d[v] = d[u] + w;
    f[v] = f[u];
}if(!vis[v] && f[v]) q.push(v),vis[v] = 1;

......

f[u] = 0;//出队时清掉f

我们依次分析一下以上代码:

当扩展到终点时,我们应当直接跳过他(注意不是终止函数),用终点再去扩展终点的出边可能会导致答案重复统计。

显然,当从 (u)(v) 的权与 (u) 当前最短路的和与当前 (v) 的最短路相等时,我们将 (v) 点的方案数加上 (u) 点的方案数。

如果最短路被更新,那么 (v) 原有的方案全部作废,新方案数与 (u) 方案数相同。

为什么入队有两个判断条件呢?第一个条件是显然的,本来就在队列中的点不应重复入队。对于第二个条件,显然只有进入了以上两个分支, (f[v]) 才会被更新。该条件保证了不扩展冗余的节点。

由于 (spfa) 的原理,每个节点可能入队多次,如果不清空 (f) 数组可能导致答案的重复统计,因此在离队时要清掉。


一个小小的题外话

关于 (spfa) ,我们发现由于其有着节点可能会多次进队的特殊性质,因此在运用它不论是进行什么样的操作,尤其是递推式更新时,一定要注意重复扩展的问题,或者换用 (dijkstra) 算法司普发早死几百年了

对于本题 (30pts),由于 (dijkstra) 算法改动部分大同小异,因此这里不再赘述,仅保留以上添加代码的第二部分并将其改为 (dijkstra) 风格即可。


(70pts)

考虑没有 (0) 边的情况,由于 (K = 50) 我们来考虑一下 (dp) 做法。

如何设计状态呢?一个显而易见的思路如下:

我们现在已经计算出了到 (n) 点的最短路 (d[n]),显然题设要求路径长度不得超过 (d[n] + K)。不妨令 (f[i][j]) 表示在 (i) 号点,剩下可以走的路的长度是 (j)

初态 : (f[1][d[n] + k] = 1;)

末态 : (ans += f[n][j];) (j)(d[n] -> d[n] + k)

转移 : (E(u,v,w)),(f[v][j - w] += f[u][j];)

(d[n]) 可能很大,开不下,怎么办?

注意到我们有从 (1) 到每个点的最短路径长。同时观察终态我们可以发现统计答案时只用到了 (d[n] -> d[n] + k)。那么考虑在第二维只记录 (K)(f[i][j]) 表示在 (i) 号点,剩下可以多走的路的长度是 (j)。很显然,我们只需要在走的过程中逐步计算相对于最短路多走的路径长,保证这个多出来的长小于 (K) 即可。

不过,这个做法的正确性似乎有待商榷。我们知道全局最短路并不完全是局部最短路,从 (u)(v) 的最优路径也不一定是 (d[v] + d[u]) ,但这种思路处理本题足矣。

希望有想法的读者能在评论区留下您的看法,对此法证明或证伪,鄙人感激不尽。

(100pts)

考虑满分做法。当 (0) 边存在时,显然只有出现 (0) 环时无解。我们对递归中的状态打上一个标记,一旦发现尚在递归中的状态访问到自身时(访问同一节点且剩余路长相等)则返回无解。

AC代码

#include<iostream>
#include<cctype>
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
const int maxn = 100005;
int read(){
	int re = 0,ch = getchar();
	while(!isdigit(ch)) ch = getchar();
	while(isdigit(ch)) re = (re<<1) + (re<<3) + ch - '0',ch = getchar();
	return re; 
}
struct edge{
	int v,w,nxt;
}e[maxn<<1];
int h[maxn],cnt;
void add(int u,int v,int w){
	e[++cnt].w = w;
	e[cnt].v = v;
	e[cnt].nxt = h[u];
	h[u] = cnt;
}
int n,m,k,p,t;
bool vis[maxn];
int dis[maxn];
int f[maxn][55];
bool v[maxn][55];
void spfa(){
	memset(dis,0x3f,sizeof(dis));
	queue<int> q;
	vis[1] = 1;
	dis[1] = 0;
	q.push(1);
	while(q.size()){
		int u = q.front();q.pop();
		vis[u] = 0;
		for(int i = h[u];i;i = e[i].nxt){
			int v = e[i].v,w = e[i].w;
			if(dis[v] > dis[u] + w){ 
				dis[v] = dis[u] + w;
				if(!vis[v]) q.push(v),vis[v] = 1;
			}
		}
	}
}
int dfs(int u,int left){
	if(left < 0) return 0;
	if(v[u][left]) return -1;// 0 ring
	if(f[u][left] != -1) return f[u][left];
	v[u][left] = 1;
	int ans = 0;
	if(u == n) ans++;
	for(int i = h[u];i;i = e[i].nxt){
		int v = e[i].v,w = e[i].w;
		int ansc = dfs(v,left - (dis[u] + w - dis[v]));
		if(ansc == -1) return -1;
		ans = (ansc + ans) % p;
	}
	f[u][left] = ans % p;
	v[u][left] = 0;
	return ans;
}
int main(){
	t = read();
	while(t--){
		n = read(),m = read(),k = read(),p = read();
		memset(h,0,sizeof(h));
		memset(f,-1,sizeof(f));
		memset(v,0,sizeof(v));
		cnt = 0;
		for(int i = 1;i <= m;i++){
			int u = read(),v = read(),w = read();
			add(u,v,w);
		}
		spfa();
		printf("%d
",dfs(1,k));
	}
	return 0;
}
原文地址:https://www.cnblogs.com/mysterious-garden/p/9892134.html