解析·NOIP·冷门 CLZ最小环

 赐予我力量,去改变我所能改变的;赐予我勇气,去接受我不能改变的;并赐予我智慧,去分辨这两者。

-----安东尼达斯

NOIP的图论题中有一部分是跟图上的环有关的。蒟蒻的我在USACO上刷题时发现了一种(或许)还没有普及的快速最小环算法,拥有极高的效率,可以在求最短路的时间复杂度内求出经过任意一点的最小环大小或权值。作者将它称作Calculate lacework zoomly shortest cyclic,这里暂译作CLZ最小环
与Floyd求图上最小环不同,CLZ最小环还可以很快捷地求出经过特定点的最小环。
CLZ最小环的思路也极为简单,很容易理解:对于求经过一个点s的最小环时,即首先进行最短路操作,在进行第一次松弛操作后重新将该点标记为未访问,并重新访问。由于是与最短路同时运作,所以其复杂度几乎取决于最短路算法。当然,CLZ最小环仍有其独立的优化方案。

朴素情况

首先让我们考虑朴素情况,非负带环有向图。对于求最小环中所含的点数,我们只需要将每一条边的权值修改为1,再按上述过程操作即可。
至于如何回退操作,相信不用细讲。我们可以在最短路算法的最前定义一个布尔型变量bool wait初始值为1,在进行完松弛操作后判断wait的状态来决定是否需要将起点重新放入。

void dijkstra(int u){        bool wait=1;     /*     blblblblblbl     */     if(wait){        dis[u]=0x3f3f3f3f,vis[u]=0;//对单个点进行初始化        q.push(u);//将点重新放入        wait=0;//设定当前松弛次数     } }

函数代码如下

void dijkstra(int u){     pall note;     bool wait=1;     for(register int i=0;i<maxn;i++)dis[i]=0x3f3f3f3f,vis[i]=0;     dis[u]=0;vis[u]=1;     set<pall,cmp> s;     s.insert(make_pair(u,0));     for(register int i=0;i<n && s.size();i++){         set<pall,cmp>::iterator it=s.begin();         u=it->X;         s.erase(*it);         vis[u]=1;         for(register int j=p[u];~j;j=E[j].next){             int v=E[j].v;             int w=E[j].w;             if(dis[v]>dis[u]+w && !vis[v]){                 s.erase(make_pair(v,dis[v]));                 s.insert(make_pair(v,dis[v]=dis[u]+w));                 note=make_pair(u,v);             }         }         if(wait){             dis[u]=0x3f3f3f3f;             vis[u]=0;             wait=0;         }     } }

无向图

对于无向图来说,若允许经过同一条边多次,则与上述操作无异。但若每条边只能经过一次,则需要一些额外的操作。因为无向图的性质,我们可以知道,从u