• 洛谷 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;
    }
    
  • 相关阅读:
    idea 设置注释
    SVN解决冲突
    mysql执行 sql文件遇到USING BTREE ) ENGINE=MyISAM DEFAULT CHARSET=utf8错误
    如何查看JDK以及JAVA框架的源码
    一道常被人轻视的前端JS面试题
    Js 常用调试的方法
    主要的Ajax框架都有什么?
    Ajax使用的五步法
    Java正则表达式
    查出在当天所处的日期区间的某些数据
  • 原文地址:https://www.cnblogs.com/mysterious-garden/p/9892134.html
Copyright © 2020-2023  润新知