Codeforces 题面传送门 & 洛谷题面传送门

大家都是暴力找生成树然后跳路径,代码不到 50 行(暴论)的一说……好,那本蒟蒻决定提供一种代码 150 行,但复杂度也是线性的分类讨论做法。

首先大家都是从“如果存在两个环相交,就一定存在符合要求的路径”这个性质入手的,而我不是。注意到题目条件涉及“简单路径”,因此我首先想到的是,如果两个点 \(u,v\) 之间存在三条互不相交的路径,那么 \(u,v\) 在同一个点双连通分量中必定是必要条件,因此不同点双之间的点必然是没有贡献的,我们只用考虑同一点双中的点即可。

考虑什么样的点双中存在符合条件的两个点,通过手玩数据我发现,一个环的情况显然是不行的,其余情况都存在符合要求的两个点。因此直觉告诉我,一个点双中存在符合条件的两个点的充要条件就是这个点双不是一个环。事实也的确如此,考虑怎么构造符合条件的路径。

我们考虑随便定一个根节点,并以从根节点开始 DFS 找出点双的一棵 DFS 树,那么由于原图是一个点双,必然有与根节点 \(r\) 相连的树边只有一条,因为根据 DFS 树的性质,该点双中所有非树边都是连接 DFS 树上某个点与其祖先的边,因此如果根节点存在多个分叉,那么这些分叉代表的子树之间两两是没有边的,换句话说,去掉根节点后图不连通,不符合点双的定义。考虑就此分三种情况讨论:

  1. 存在两个及以上与根节点相连的非树边

    假设这两条非树边分别是 \(r\to u\) 和 \(r\to v\),那么考虑找出 \(w=\text{LCA}(u,v)\),那么 \(r,w\) 之间存在三条符合要求的路径,一条是 \(w\) 直接跳到 \(r\),一条是 \(w\) 向下走到 \(u\),\(u\) 再到 \(r\),一条是 \(w\) 向下走到 \(v\),\(v\) 再到 \(r\)​​。

    如下图所示,三种颜色分别代表了三条不同的路径:

  2. 只有一条与根节点相连的非树边

    显然,由于原图是一个点双,不能不存在非树边与根节点相连,否则去掉根节点唯一的儿子后,图就不连通了(这里假定点双大小 \(\ge 3\),如果点双大小 \(\le 2\) 直接判掉即可)

    那么我们不妨假设这条非树边为 \(r\to u\),到这里我们继续分类讨论:

    1. 如果 \(u\) 不是叶子

      那么我们考虑找到 \(u\) 子树中一个叶子 \(v\),再找到所有与 \(v\) 相连的点中,深度最浅的那个点 \(w\),还是根据图是一个点双这个性质,必然有 \(w\) 的深度严格浅于 \(u\) 的深度,否则去掉 \(w\) 之后图不连通,这样考虑 \(u\to w\) 的路径,有以下三条:

      • \(u\) 向上直接跳到 \(w\)
      • \(u\) 跳到根节点 \(r\),再向下走到 \(w\)
      • \(u\) 向下走到 \(v\),再向上跳到 \(w\)​

    2. 如果 \(u\) 是叶子

      我们考虑找到一条非树边,不同于 \(r\to u\) 这条边,并且这两条边至少有一个端点在 \(u\to r\) 这条链上,可以说明我们总能找到这样的边,否则图不满足点双的性质,读者自证不难,那么我们假设这条边为 \(x,y\),其中 \(x\) 为深度较小者,设离 \(y\) 最近的、在 \(u\to r\) 这条链上的节点为 \(w\),那么考虑构造这样三条 \(w\to x\) 的路径:

      • \(w\) 直接向上跳到 \(x\)
      • \(w\) 向下走到 \(y\),再走到 \(x\)
      • \(w\) 向下走到 \(u\),跳到 \(r\),再向下走到 \(x\)​

分类讨论一下即可,复杂度 \(\mathcal O(n)\)

const int MAXN=2e5;
int n,m,U[MAXN+5],V[MAXN+5];
struct graph{
int hd[MAXN+5],to[MAXN*2+5],nxt[MAXN*2+5],ec=1;
void adde(int u,int v){to[++ec]=v;nxt[ec]=hd[u];hd[u]=ec;}
} g,ng,t;
int dfn[MAXN+5],low[MAXN+5],tim=0;
int stk[MAXN+5],tp=0,bel[MAXN+5],cmp=0,in_stk[MAXN+5];
void tarjan(int x){
// printf("tarjan %d\n",x);
dfn[x]=low[x]=++tim;
for(int e=g.hd[x];e;e=g.nxt[e]){
int y=g.to[e];
if(!dfn[y]){
stk[++tp]=e>>1;in_stk[e>>1]=1;
tarjan(y);chkmin(low[x],low[y]);
if(low[y]>=dfn[x]){
// printf("find an edcc %d %d\n",x,y);
cmp++;int o;do{
o=stk[tp--];bel[o]=cmp;
in_stk[o]=0;
} while(o^(e>>1));
}
} else {
chkmin(low[x],dfn[y]);
if(dfn[y]<dfn[x]&&!in_stk[e>>1]){
stk[++tp]=e>>1;in_stk[e>>1]=1;
}
}
}
}
vector<int> bp[MAXN+5];
int in[MAXN+5],fa[MAXN+5],dep[MAXN+5],deg[MAXN+5];
bool vis[MAXN+5],ont[MAXN+5];
void dfs(int x){
vis[x]=1;
for(int e=ng.hd[x];e;e=ng.nxt[e]){
int y=ng.to[e];
if(!vis[y]){
ont[e>>1]=1;t.adde(x,y);
fa[y]=x;deg[x]++;deg[y]++;dep[y]=dep[x]+1;dfs(y);
}
}
}
void prt(vector<int> res1,vector<int> res2,vector<int> res3){
puts("YES");
printf("%d",res1.size());for(int x:res1) printf(" %d",x);printf("\n");
printf("%d",res2.size());for(int x:res2) printf(" %d",x);printf("\n");
printf("%d",res3.size());for(int x:res3) printf(" %d",x);printf("\n");
exit(0);
}
void work(int id){
for(int e:bp[id]) ng.adde(U[e],V[e]),ng.adde(V[e],U[e]);
dfs(U[bp[id][0]]);int rt=U[bp[id][0]],cnt=0;
for(int e=ng.hd[rt];e;e=ng.nxt[e]) cnt+=(!ont[e>>1]);
assert(cnt>=1);
if(cnt>=2){
int x=0,y=0;vector<int> p1,p2,p3;p1.pb(rt),p2.pb(rt);
for(int e=ng.hd[rt];e;e=ng.nxt[e]) if(!ont[e>>1]){
if(!x) x=ng.to[e];else if(!y) y=ng.to[e];
} if(dep[x]<dep[y]) swap(x,y);
while(dep[x]>dep[y]) p1.pb(x),x=fa[x];
while(x^y) p1.pb(x),p2.pb(y),x=fa[x],y=fa[y];
p1.pb(x);p2.pb(x);
while(x^rt) p3.pb(x),x=fa[x];p3.pb(rt);
reverse(p3.begin(),p3.end());prt(p1,p2,p3);
} else {
int p=0,pe=0;
for(int e=ng.hd[rt];e;e=ng.nxt[e]) if(!ont[e>>1])
p=ng.to[e],pe=e>>1;
if(deg[p]!=1){
int q=p;while(deg[q]!=1){
for(int e=t.hd[q];e;e=t.nxt[e])
if(t.to[e]!=fa[q]){q=t.to[e];break;}
} int r=0;for(int e=ng.hd[q];e;e=ng.nxt[e])
if(!r||dep[r]>dep[ng.to[e]]) r=ng.to[e];
vector<int> p1,p2,p3;int cp=p,cq=q;
while(cp^r) p1.pb(cp),cp=fa[cp];p1.pb(cp);
while(cp^rt) p2.pb(cp),cp=fa[cp];p2.pb(rt);
reverse(p2.begin(),p2.end());p2.insert(p2.begin(),p);
while(cq^p) p3.pb(cq),cq=fa[cq];p3.pb(cq);
reverse(p3.begin(),p3.end());p3.pb(r);
prt(p1,p2,p3);
} else {
int cp=p;static bool on_ch[MAXN+5]={0};
while(cp^rt) on_ch[cp]=1,cp=fa[cp];on_ch[rt]=1;
int u=0,v=0;vector<int> p1,p2,p3;
for(int e=1;e<=(ng.ec>>1);e++) if(!ont[e]&&e!=pe){
int x=ng.to[e<<1],y=ng.to[e<<1|1];
if(on_ch[x]||on_ch[y]){u=x;v=y;break;}
} if(dep[u]<dep[v]) swap(u,v);
while(!on_ch[u]) p1.pb(u),u=fa[u];p1.pb(u);
reverse(p1.begin(),p1.end());p1.pb(v);
int cu=u,cv=v;while(cu^v) p2.pb(cu),cu=fa[cu];p2.pb(v);
cu=p;while(cv^rt) p3.pb(cv),cv=fa[cv];p3.pb(rt);
while(cu^u) p3.pb(cu),cu=fa[cu];p3.pb(cu);
reverse(p3.begin(),p3.end());prt(p1,p2,p3);
}
}
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
scanf("%d%d",&U[i],&V[i]);
g.adde(U[i],V[i]);g.adde(V[i],U[i]);
} for(int i=1;i<=n;i++) if(!dfn[i]) tarjan(i);
for(int i=1;i<=m;i++) bp[bel[i]].pb(i);
// for(int i=1;i<=m;i++) printf("%d\n",bel[i]);
for(int id=1;id<=cmp;id++){
int cc=0;
for(int e:bp[id]){
if(!in[U[e]]) in[U[e]]=1,cc++;
if(!in[V[e]]) in[V[e]]=1,cc++;
} if(cc<bp[id].size()){
work(id);
} for(int e:bp[id]) in[U[e]]=in[V[e]]=0;
} puts("NO");
return 0;
}

Codeforces 521E - Cycling City(点双连通分量+分类讨论)的更多相关文章

  1. D8 双连通分量

    记得有个梗那一天,zw学生zzh大佬说逃不掉的路变成a不掉的题哈哈哈哈: 分离的路径: BZOJ 1718POJ 3177LUOGU 286: 思路:在同一个边双连通分量中,任意两点都有至少两条独立路 ...

  2. [Codeforces 555E]Case of Computer Network(Tarjan求边-双连通分量+树上差分)

    [Codeforces 555E]Case of Computer Network(Tarjan求边-双连通分量+树上差分) 题面 给出一个无向图,以及q条有向路径.问是否存在一种给边定向的方案,使得 ...

  3. codeforces 962F.simple cycle(tarjan/点双连通分量)

    题目连接:http://codeforces.com/contest/962/problem/F 题目大意是定义一个simple cycle为从一个节点开始绕环走一遍能经过simple cycle内任 ...

  4. CodeForces 97 E. Leaders(点双连通分量 + 倍增)

    题意 给你一个有 \(n\) 个点 \(m\) 条边的无向图,有 \(q\) 次询问,每次询问两个点 \(u, v\) 之间是否存在长度为奇数的简单路径. \(1 \le n, m, q \le 10 ...

  5. Simple Cycles Edges CodeForces - 962F(点双连通分量)

    题意: 求出简单环的所有边,简单环即为边在一个环内 解析: 求出点双连通分量,如果一个连通分量的点数和边数相等,则为一个简单环 点双连通分量  任意两个点都至少存在两条点不重复的路径  即任意两条边都 ...

  6. Gym - 100676H H. Capital City (边双连通分量缩点+树的直径)

    https://vjudge.net/problem/Gym-100676H 题意: 给出一个n个城市,城市之间有距离为w的边,现在要选一个中心城市,使得该城市到其余城市的最大距离最短.如果有一些城市 ...

  7. 【Codefoces487E/UOJ#30】Tourists Tarjan 点双连通分量 + 树链剖分

    E. Tourists time limit per test: 2 seconds memory limit per test: 256 megabytes input: standard inpu ...

  8. HDU 3686 Traffic Real Time Query System(双连通分量缩点+LCA)(2010 Asia Hangzhou Regional Contest)

    Problem Description City C is really a nightmare of all drivers for its traffic jams. To solve the t ...

  9. POJ3352 Road Construction (双连通分量)

    Road Construction Time Limit:2000MS    Memory Limit:65536KB    64bit IO Format:%I64d & %I64u Sub ...

随机推荐

  1. js判断移动端浏览器类型,微信浏览器、支付宝小程序、微信小程序等

    起因 现在市场上各种跨平台开发方案百家争鸣各有千秋,个人认为最成熟的还是hybird方案,简单的说就是写H5各种嵌入,当然作为前端工程师最希望的也就是公司采用hybird方案当作技术路线. 所谓的hy ...

  2. 大闸蟹的 O O 战记

    一. 第四单元架构设计分析 第一次作业,UML类图 第一次作业的主要任务是完成对UML类图的解析并实现查询等操作,需要在课程组给定的框架中添加函数.对于UML类图,其存储是按照元素来存储的,其将所有的 ...

  3. 同人逼死官方系列!从 DDC 嗅探器到 sddc_sdk_lib 的数据解析

    从 DDC 嗅探器到 sddc_sdk_lib 的数据解析 之前的 DDC 协议介绍 主要讲了设备加入.退出以及维持设备状态,而 SDK框架 sddc_sdk_lib 解析 主要讲了 SDK 库的结构 ...

  4. hdu 2860 Regroup(并查集)

    题意: AP x yA recruit with ability rate x were asked to join company y. (0<=x<2^31, 0<=y<n ...

  5. POJ 1442 Air Raid(DAG图的最小路径覆盖)

    题意: 有一个城镇,它的所有街道都是单行(即有向)的,并且每条街道都是和两个路口相连.同时已知街道不会形成回路. 可以在任意一个路口放置一个伞兵,这个伞兵会顺着街道走,依次经过若干个路口. 问最少需要 ...

  6. hdu 1847 Good Luck in CET-4 Everybody! (简单博弈)

    题意: n张牌,双方轮流抓取.每人每次抓取的牌数必须是2的幂次(1,2,4,8...). 最后抓完的人胜. 思路 : 考虑剩3张牌,后手胜. 考虑3的倍数.假设先抓者当轮抓2x 张,2x %3等于1或 ...

  7. JavaScript复习 1

    概括及使用方法: JavaScript编写规范 一般放在<head>-</head>中间 逐行被执行,越短越好 大小写敏感 语句是基本单位 通常以分号表示语句结束 多行语句可以 ...

  8. 印象最深的一个bug——排查修复问题事件BEX引发的谷歌浏览器闪退崩溃异常

    前言 最近,我们部门负责项目运维的小王频频接到甲方的反馈,运行的项目使用谷歌浏览器登录后,每次点击处理2秒后,浏览器自动闪退崩溃.小王同学折腾了一个星期,还没找到问题的原因.甲方客户都把问题反馈给项目 ...

  9. QuantumTunnel:协议路由 vs 端口路由

    本篇来聊一下内网穿透中流量转发的问题 内网穿透和核心逻辑是根据流量的路由信息准确地将公网流量路由到指定的机器端口上,从而完成一次流量的内网穿透. 这里有一个核心问题,路由信息从哪里获取? 常见的有将路 ...

  10. 【java+selenium3】线程休眠方法 (六)

    一.线程休眠的方法   Thread -- sleep 调用方式: Thread.sleep(long millis) 建议:不推荐使用此方式来等待,因为元素的实际渲染时间未知,长时间的等待则浪费的时 ...