P5234 [JSOI2012]越狱老虎桥 题解
前言:好多题解被 hack 了啊。
Problem
题目大意:给定一张图,
数据范围:
Solution
前置芝士:边双连通分量。
显然,边双中的边是肯定不能删的,因为删除了也并不会使图不连通。所以我们有个自然的想法,我们可以把边双缩点,在重新建图,这样我们就简化为了树上问题。
然后我们考虑树上怎么做,想一想会发现,
然后我们可以考虑,从小到大枚举每一条边,看看枚举的这条边和之前的边能否构成一条链,如果不能构成那么显然这条边就是答案,否则若所有边都可以构成一条链,那么输出
至于如何判断能否构成链,可以发现,我们以最小的边的任意一个端点作为根节点,那么除了根节点,显然每一个点只能有一个点作为它的儿子,若新加边的端点往上跳,若跳到一个点有儿子且不是自己,那么就够不成链。根节点要特判一下。
至于时间复杂度,本来我想用倍增的,但是我们可以发现,每一个点只会被更新一次,更新第二次要么直接退出(即发现构不成链),或者不用往上跳了(显然,当前节点满足,祖先节点显然不影响)。所以暴力跳是均摊的复杂度,是比非均摊的倍增还快的。
结合代码感性理解一下哈!
截止
Code
#include<bits/stdc++.h>
using namespace std;
const int N=2e6+7232;
int n,m,x,y,z;
struct hl{
int v,nxt,w;
}e[N];
struct len{
int u,v,w;
}t[N];
int h[N],cnt=1;
bitset<N> vis;
void add(int u,int v,int w)
{
e[++cnt].v=v;e[cnt].nxt=h[u];h[u]=cnt;e[cnt].w=w;
}
int mi(int x,int y){return x<y?x:y;}
int dfn[N],low[N],tot,now;
void tarjan(int x,int fx)
{
dfn[x]=low[x]=++tot;
for(int i=h[x];i;i=e[i].nxt)
{
if(e[i].v==fx) continue;
if(!dfn[e[i].v])
{
tarjan(e[i].v,x);
low[x]=mi(low[x],low[e[i].v]);
if(low[e[i].v]>dfn[x]) vis[i]=vis[i^1]=1;
}
else low[x]=mi(low[x],dfn[e[i].v]);
}
}
int ecc[N];
void dfs(int x)
{
ecc[x]=now;
for(int i=h[x];i;i=e[i].nxt)
{
if(vis[i]||ecc[e[i].v]) continue;
dfs(e[i].v);
}
}
bool cmp(len x,len y)
{
return x.w<y.w;
}
int fa[N],dis[N],dep[N],son;
void dfss(int x,int fx)
{
fa[x]=fx;dep[x]=dep[fx]+1;
for(int i=h[x];i;i=e[i].nxt)
{
if(e[i].v==fx) continue;
dfss(e[i].v,x);
}
}
int num;
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
{
scanf("%d%d%d",&x,&y,&z);
add(x,y,z);add(y,x,z);
t[i]={x,y,z};
}
for(int i=1;i<=n;i++) if(!dfn[i]) tarjan(i,i);
for(int i=1;i<=n;i++) if(!ecc[i]) ++now,dfs(i);//找边双
memset(h,0,sizeof(h));cnt=0;
for(int i=1;i<=m;i++)
if(ecc[t[i].u]!=ecc[t[i].v]) add(ecc[t[i].u],ecc[t[i].v],t[i].w),add(ecc[t[i].v],ecc[t[i].u],t[i].w),t[++num]=t[i];
//重新加边
sort(t+1,t+num+1,cmp);
dfss(ecc[t[1].u],0);dis[ecc[t[1].u]]=ecc[t[1].v];//以最小的边的一个端点作为根
for(int i=2;i<=num;i++)
{
int k=dep[ecc[t[i].u]]>dep[ecc[t[i].v]]?ecc[t[i].u]:ecc[t[i].v];//从深度深的跳
while(!dis[fa[k]]) dis[fa[k]]=k,k=fa[k];//跳到一个固定儿子的点
if(son==0&&fa[k]==ecc[t[1].u]&&dis[fa[k]]!=k) son=k;//若为根,特判
else if(dis[fa[k]]==k||fa[k]==ecc[t[1].u]&&son==k) ;//若当前节点的父亲在链上的儿子就是自己,那么退出
else //否则输出边的权值
{
printf("%d\n",t[i].w);
return 0;
}
}
printf("-1\n");
}