题解 CF962F 【Simple Cycles Edges】
TUIFEI_oier · · 题解
一个奇怪的做法,貌似没有看见相同做法的题解。
首先对于不同的连通块,我们分别处理即可。
发现如果一条边符合条件,当且仅当它所在的点双恰为一个简单环。
于是,我们可以把题意转化为:求所有点数等于边数的点双连通分量。
因为一条边只会在一个点双中出现,于是我们考虑在每条边上新增一个点。
即把这样一张图:
变成这样:
此时,如果我们把原来的
这是在 Tarjan 过程中可以方便地统计的。
同时,因为每条边只会属于一个点双,所以每个新增点也只会被一个点双包含,记录下来每个新增点对应的点双编号即可。
此时还有一个问题:对于一条不存在于任意一个简单环中的边,我们这样处理会导致误判。
有一个比较笨的解决方法,在新增点之前跑一下原图的 Tarjan,则所有只有两个点的点双就对应一条这样的边,记录下来这些边后新建点时忽略这些边即可。
于是总时间复杂度为
代码如下:
#include <bits/stdc++.h>
using namespace std;
#define ch getchar
template<typename T> void read(T &x){
x = 0; int f(1); char c = ch();
for(;!isdigit(c);c = ch()) if(c == '-') f = -1;
for(;isdigit(c);c = ch()) x = x*10+c-'0';
x *= f;
}
template<typename T,typename... Args>
inline void read(T &x,Args&... args){
read(x); read(args...);
}
#define pb emplace_back
const int maxn = 100005;
int n,m,ANS[maxn],cnt;
int hd[maxn],tt = 1; // 注意前向星的开始下标。
struct edge{int ed,nxt;}e[2*maxn];
int dfn[2*maxn],low[2*maxn];
int sta[2*maxn],top,tot;
int val[2*maxn],V[2*maxn];
int eu[maxn],ev[maxn];
vector<int> Frm[2*maxn];
vector<int> G[2*maxn];
void star(int u,int v){
e[++tt] = (edge){v,hd[u]}; hd[u] = tt;
e[++tt] = (edge){u,hd[v]}; hd[v] = tt;
}
void Tarjan(int x){
dfn[x] = low[x] = ++tot;
sta[++top] = x;
for(auto y:G[x]){
if(!dfn[y]){
Tarjan(y);
low[x] = min(low[x],low[y]);
if(low[y] == dfn[x]){
++cnt;
while(sta[top+1] != y){
V[cnt] += val[sta[top]];
if(val[sta[top]] == -1)
Frm[cnt].pb(sta[top]);
--top;
}
V[cnt] += val[x];
if(val[x] == -1) Frm[cnt].pb(x);
// 统计点权和,并记录每个点双中的新增点。
}
}
else low[x] = min(low[x],dfn[y]);
}
}
void tarjan(int x){
dfn[x] = low[x] = ++tot;
sta[++top] = x;
for(int i = hd[x];i;i = e[i].nxt){
int y = e[i].ed;
if(!dfn[y]){
tarjan(y);
low[x] = min(low[x],low[y]);
if(low[y] == dfn[x]){
cnt = 0;
while(sta[top+1] != y)
++cnt,--top;
++cnt;
if(cnt == 2) ANS[i/2] = 1;
// 对于所有大小为 2 的点双,给边打上标记。
}
}
else low[x] = min(low[x],dfn[y]);
}
}
int main(){
read(n,m);
for(int i(1);i <= m;++i){
read(eu[i],ev[i]);
star(eu[i],ev[i]);
}
for(int i(1);i <= n;++i)
if(!dfn[i]) tarjan(i),--top;
//第一次 tarjan,求出不存在于任意一个简单环中的边,并打上标记。
memset(dfn,0,sizeof dfn);
tot = top = cnt = 0;
for(int i(1);i <= n;++i) val[i] = 1;
for(int i(1);i <= m;++i){
if(ANS[i] == 1){ANS[i] = 0;continue;}
G[eu[i]].pb(n+i); G[ev[i]].pb(n+i);
G[n+i].pb(eu[i]); G[n+i].pb(ev[i]); val[n+i] = -1;
}
//新建边上的点。
n += m;
for(int i(1);i <= n;++i)
if(!dfn[i]) Tarjan(i),--top;
//第二次 tarjan,求出所有新图点双的权值和。
for(int i(1);i <= cnt;++i)
if(V[i] == 0)
for(int j(0);j < Frm[i].size();++j)
ANS[Frm[i][j]-n+m] = 1;
//统计答案、输出。
cnt = 0;
for(int i(1);i <= m;++i) cnt += ANS[i];
cout << cnt << endl;
for(int i(1);i <= m;++i)
if(ANS[i]) printf("%d ",i);
return 0;
}