zoukankan      html  css  js  c++  java
  • [JZOJ2702] 【GDKOI2012模拟02.01】探险

    题目

    在这里插入图片描述

    题目大意

    给你一个每条边正反权值不一定相同的无向图,求起点为11点的最小环。


    思考历程

    一看到这题,就觉得是一个经典模型。
    然后思考先前做过最小环的经历,发现没个卵用。
    我突然想到,既然这一个环是在11点上的,那么肯定有两条边和11相连。
    一个很显然的思路就是,枚举与11相连的边,然后计算带着这条边最小环。
    首先处理一个最短路,并且在带一个前驱,表示从哪一个边转移过来。
    枚举和11相连的每一条边,设另一个点为xx,如果xx的前驱不是11,那么直接为dis(x)+len(x,1)dis(x)+len(x,1)
    问题来了,前驱是11怎么办?
    有一个很暴力的思路,就是将这条边删掉,再跑一遍最短路径。
    时间肯定会炸啊!(后来:呵呵)
    其实我们只需要保证再求一个最短路,使得这个最短路的前驱不是11,那就可以搞定了。
    我们还是要保证这个尽量短,所以这是一个次短路。
    现在的瓶颈就是,如何求次短路呢?
    于是我懵逼了,求次短路万一不能保证时间复杂度怎么办?
    最后想不到什么稳妥的解法,于是暴力一点,就将边删去,然后跑最短路。
    结果……100分?
    出题人啊,想想你的数据,让一个O(m2)O(m^2)的蒟蒻都过了。


    水法

    特意新增这个栏目……
    其实我的这个做法算是水法吧,只不过这明明是60分的方法啊……
    SLS大佬,用暴力跑过了这题!
    怎么暴力?用IDA*!
    二分一下环的长度,然后递归来干!
    设一个估价函数,就是它到11的最短路径。
    然后加了各种剪枝……
    比如说,如果在递归的过程当中出现了环,就退出……
    反正是一堆优化。
    然后轻易地水过这题。


    正解

    先说一说第一个正解。
    其实我想得已经非常接近了,就是求最短路和次短路啊!
    怎么求次短路呢?题解中解释得有些笼统,就是说用SPFA不停迭代直到稳定为止。
    原来,事情并没有我想象中的那么复杂……
    就是在转移的时候多了一个次短路的转移而已……其实也不用特意在意它是次短的,只需要第一条边和最短路不一样就行了。
    然后我有一个问题,如果有点xx,它转移到yy,不如yy的次短路,所以会被踢掉;可是如果它继续从yy转移到zz,那又能更新zz的次短路。有没有这种情况呢?
    如果有这种情况,那就是yy原来的次短路和zz的最短路的初始边相同,但xx的次短路和zz的最短路的初始边不同,所以就有可能转移过去。
    某大爷解释道:如果yy原来的次短路和zz的最短路的初始边相同,那么yy的最短路和zz的最短路的初始边一定不同,那这也是可以转移过去的。
    所以说,直接求在正确性上似乎没有什么问题。
    然后时间呢?
    时间我就不清楚了,有点玄学。

    然后就是一个比较正经的解法,时间复杂度绝对优秀(可以用Dijkstra):
    这个方法就是构造一个新图,在里面跑最短路。
    首先我们将每一个点的最短路给算出来,并且记录它们最短路经过的边(记住是边!题解害死人!不过我没有受骗),记作firfir
    设新图中源点为SS.,汇点为TT。(不要想到网络流去了!)
    对于从11连出去的边e(1,x,len)e(1,x,len)
    如果fir(x)=efir(x)=e,不要理它。
    否则,就在新图中连(S,x,len)(S,x,len)
    对于从xx连回11的边e(x,1,len)e(x,1,len)
    如果fir(x)=efir(x)=e,连(x,T,len)(x,T,len)
    否则,连(S,T,dis(x)+len)(S,T,dis(x)+len)
    对于其它的边(u,v,len)(u,v,len)
    如果fir(u)=fir(v)fir(u)=fir(v),就连(u,v,len)(u,v,len)
    否则连(S,u,dis(u)+len)(S,u,dis(u)+len)
    然后整个图就建完了,从SS点跑一遍到TT的最短路就好了。
    这个方法真的是十分巧妙,具体是为什么呢?
    我斟酌了很久……
    我们试着分个类:
    对于和11相连的边e(x,1)e(x,1),如果xx的最短路径是从11开始,走其它的边到了xx,那么答案显然是dis(x)+lendis(x)+len,对应上面的(S,T,dis(x)+len)(S,T,dis(x)+len)。那么xx不会直接连到TT,所以不会有从SS出来,走向同道路回去的尴尬情况。
    如果xx的最短路径是从11直接走这条边到xx的,通过上面我们建的图,我们可以发现,它在SS这边没有,在TT这边有,所以不可能走同一条边。
    现在有一个问题,可能出现这样的情况:最小环和11相连的两个点,11到它们的最短路径都是直接从11走到它们,那样有没有可能算漏呢?
    我们再看看边e(u,v)e(u,v)其中uuvv都不是11
    如果fir(u)=fir(v)fir(u)=fir(v),那么它们的最短路是从同一条边走过来的,不能直接组成环,所以按照原样。
    否则,它们是可以形成一个环的。设xx为边fir(u)fir(u)的除11外的那一端的点,显然xx的最短路是直接从11走过来的,可是现在这条路没了啊!(上面没有建出来)
    不怕,直接连(S,v,dis(u)+len)(S,v,dis(u)+len),直接连过去。
    我们再回顾一下上面有没有可能算漏的问题,虽然说两个点和11相连的边不能直接连通,但是它们是可以用这种方式连通的啊!所以它们是可以被计算到的。
    这样建图既可以保证所有合法的环都可以走,又可以扼杀从同一条边走回去的机会。
    时间复杂度是很稳定的,毕竟只是求两遍最短路罢了,如果你打Dijkstra,那就不会被卡。由于我比较懒惰,所以还是打了SPFA。


    代码

    using namespace std;
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    #define N 10000
    #define M 200000
    int n,m;
    struct EDGE{
    	int to,len,num;
    	EDGE *las;
    } e[M*4+1];
    int ne;
    EDGE *last1[N+2],*last2[N+2];
    inline void link(EDGE *last[],int u,int v,int len,int num){
    	e[++ne]={v,len,num,last[u]};
    	last[u]=e+ne;
    }
    int dis[N+2],fir[N+2];
    inline void SPFA(EDGE*[],int);
    int S,T;
    int ans=2147483647;
    int main(){
    	scanf("%d%d",&n,&m);
    	for (int i=1;i<=m;++i){
    		int u,v,len1,len2;
    		scanf("%d%d%d%d",&u,&v,&len1,&len2);
    		link(last1,u,v,len1,i);
    		link(last1,v,u,len2,i);
    	}
    	SPFA(last1,1);
    	S=1,T=n+1;
    	for (EDGE *ei=last1[1];ei;ei=ei->las)
    		if (fir[ei->to]!=ei->num)
    			link(last2,S,ei->to,ei->num,ei->len);
    	for (int i=2;i<=n;++i)
    		for (EDGE *ei=last1[i];ei;ei=ei->las)
    			if (ei->to==1){
    				if (fir[i]==ei->num)
    					link(last2,i,T,ei->len,ei->num);
    				else
    					ans=min(ans,dis[i]+ei->len);//本来是S到T连这么一条边,实际上直接统计入答案也可以
    			}
    			else{
    				if (fir[i]==fir[ei->to])
    					link(last2,i,ei->to,ei->len,ei->num);
    				else
    					link(last2,S,ei->to,dis[i]+ei->len,ei->num);
    			}
    	SPFA(last2,S);
    	ans=min(ans,dis[T]);
    	printf("%d
    ",ans);
    	return 0;
    }
    #define an 1048575
    int q[an+2];
    bool inq[N+2];
    inline void SPFA(EDGE *last[],int S){
    	memset(dis,127,sizeof dis);
    	dis[S]=0;
    	int h=-1,t=0;
    	q[0]=S;
    	inq[S]=1;
    	do{
    		++h&=an;
    		for (EDGE *ei=last[q[h]];ei;ei=ei->las)
    			if (dis[q[h]]+ei->len<dis[ei->to]){
    				dis[ei->to]=dis[q[h]]+ei->len;
    				fir[ei->to]=((q[h]==1)?ei->num:fir[q[h]]);
    				if (!inq[ei->to]){
    					inq[ei->to]=1;
    					q[++t&=an]=ei->to;
    				}
    			}
    		inq[q[h]]=0;
    	}
    	while (h!=t);
    }
    

    总结

    首先,有的时候暴力出奇迹。
    遇到某些题目的时候,如果想不出正解,那就试着用暴力来做,有时几个剪枝就可以得到好多好多的分数。
    然后就是面对一些看似很难解的题目,试着转换一下模型,用各种奇妙的方式建立一个奇妙的东西。

  • 相关阅读:
    20145319 《信息安全系统设计基础》第0周学习总结
    20145319 《java程序设计》课程总结
    20145319 第十周学习总结
    20145319 实验五
    20145319 实验四
    20145319 第九周学习总结
    20145319 第八周学习总结
    20145319 实验三
    20145319 第七周学习总结
    20145312 《Java程序设计》第六周学习总结
  • 原文地址:https://www.cnblogs.com/jz-597/p/11145248.html
Copyright © 2011-2022 走看看