题目意思:
给你一个n个点m条边的无向图, 求简单环(度数为2的连通子图)个数。 (n≤1e5,n−1≤m≤n+15,保证图联通)
思路:
看了 m 的范围, 就要想到要在树上做。
最多有 16 条多余的边, 想到 状压。 二进制枚举。
首先想一下如果 n 很小的话怎么做。
二进制枚举每次要加的边, 然后判断加上这些边能不能构成一个简单环。
这些边能不能构成一个简单环的条件是:
- 所有点的度数为 2.
- 加的这些边能连在一起。
有一个很好的性质是判断一条边在不在环上。
那就是对于一个要加入的边来说, 把边的端点向上亦或, 一直亦或到根节点
相当于给了边一个权值, 如果某个边的值是 1, 说明这个边应该在环上。
对于所有权值是 1 的边, 我们新建一个图出来, 首先判断每个点的度是不是 2, 然后判断所有点是不是连接的。
上面是 n 很小时候的做法, 所以 n 很大的时候, 我们就需要用到虚树, 把那些没有用到的点去掉。
所以, 加了一个虚树,就变成了上面的做法。
几点注意:
一:
这个题目给的是个图, 并不是个树, 所以我们要提前把那些非树边找出来:
两种方法:
- 用一个并查集, 大家都懂 。
- 用一个邻接数组存边,那么 1 号边在邻接数组里的正反边的编号就是 2 和 3, i 号 边在邻接数组里的编号就是 2i 2i+1, 首先我们dfs 一遍找树边, 然后把
tree[ i / 2]
标记为真, 代表这条边被用了。 i 就是邻接数组中的编号, 最后再 for 循环一遍, 找到那些没有被用的非树边。
二:
这个题是状压枚举, 所以每次枚举的时候, 都要新建一个图, 如果每次都清空, 那肯定超时。
所以我给每个点打一个标记, 代表是第几次枚举, 当给这个点加边的时候, 我先判断这个点的标记是不是代表当前的枚举状态, 如果不是, 那么这个时候再清空。
反思:
题目说了 对于每个 u v
都有 u < v
然后我就天真的建了单向边, 然后从一号点跑 dfs,企图把所有点跑完, 我真的是个小机灵鬼。
3 2
1 3
2 3
这个例子就跑不到所有的点。 憨憨行为。
- 下次图上找个树, 就用邻接数组找, 记住 head 的起始点要从 2 开始, 因为 2 3 的一半都是 1.
- 多次建图,不要每次都清空。 看看能不能用这次的方法。
- 树上多 16 点左右的, 想想状压。 二进制枚举。
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+100;
void dbg() {cout << endl;}
template<typename T, typename... A> void dbg(T a, A... x) {cout << a << ' '; dbg(x...);}
#define logs(x...) {cout << #x << " -> "; dbg(x);}
typedef pair<int,int>P;
int n,m,dfn[N],dep[N],stk[N],par[N][20],vfa[N],st,l[N],r[N];
int cnt = 1,Head[N],Next[N<<1],To[N<<1];
int k;
bool vis[N],tr[N];
vector<P>g;
vector<int>a,e[N];
void add(int u, int v){
++cnt; To[cnt] = v;
Next[cnt] = Head[u];
Head[u] = cnt;
}
void dfs(int u, int fa){
par[u][0] = fa; dep[u] = dep[fa] + 1;
dfn[u] = ++ dfn[0]; vis[u] = 1;
for (int i = 1; i < 20; ++i)
par[u][i] = par[par[u][i-1]][i-1];
for (int i = Head[u]; i; i = Next[i]){
if (vis[To[i]]) continue;
dfs(To[i], u);
tr[i >> 1] = 1;
}
}
int LCA(int x, int y){
if (dep[x] < dep[y]) swap(x,y);
for (int i = 19; i >= 0; --i)
if ((dep[par[x][i]] >= dep[y])) x = par[x][i];
if (x == y) return x;
for (int i = 19; i >= 0; --i)
if (par[x][i] != par[y][i]){
x = par[x][i]; y = par[y][i];
}
return par[x][0];
}
void ins(int x){
if (x == 1) return;
if (st == 1){stk[++st] = x; return; }
int t = LCA(x, stk[st]);
if (t == stk[st]) {stk[++st] = x; return;}
while(st > 1 && dfn[stk[st-1]] >= dfn[t])
vfa[stk[st]] = stk[st-1], st--;
if (t != stk[st])
vfa[stk[st]] = t, stk[st] = t;
if (vis[t] == 0) vis[t] = 1, a.push_back(t);
stk[++st] = x;
}
bool cmp(const int A, const int B) {
return dfn[A] < dfn[B];
}
void get(int k){
sort(a.begin(), a.end(), cmp);
st = 1;
stk[st] = 1;
for (int i = 0; i < k; ++i) ins(a[i]);
while(st > 1) vfa[stk[st]] = stk[st-1], st--;
}
int flag[N],deg[N],mark[N], v[N];
void _add(int x, int y, int z){
if (flag[x] != z) e[x].clear(), flag[x] = z;
if (flag[y] != z) e[y].clear(), flag[y] = z;
deg[x]++; deg[y]++;
e[x].push_back(y);
e[y].push_back(x);
}
void dfs1(int u, int z){
v[u] = z;
for (auto it: e[u]){
if (v[it] == z) continue;
dfs1(it, z);
}
}
bool solve(int x){
for (auto it: a){
deg[it] = 0; mark[it] = 0;
}
m = (int)g.size();
for (int i = 0; i < m; ++i){
if (x & (1 << i)){
for (int p = g[i].first; p; p = vfa[p]) mark[p] ^= 1;
for (int p = g[i].second; p; p = vfa[p]) mark[p] ^= 1;
}
}
for (int i = 0; i < m; ++i){
if (x & (1 << i)){
_add(g[i].first, g[i].second, x);
}
}
for (auto it: a){
if (mark[it] && vfa[it]) _add(it, vfa[it], x);
}
bool ok = 1;
for (auto it: a){
if (deg[it] && deg[it] != 2) ok = 0;
if (!ok) break;
}
for (auto it: a){
if (!ok) break;
if (deg[it]) {
dfs1(it, x);
break;
}
}
for (auto it: a){
if (!ok) break;
if (deg[it] && v[it] != x) ok = 0;
}
return ok;
}
int main(){
// freopen("in.txt", "r",stdin);
int x,y;
scanf("%d%d",&n,&m);
for (int i = 1; i <= m; ++i){
scanf("%d%d",&x,&y);
l[i] = x; r[i] = y;
add(x,y); add(y,x);
}
dfs(1, 0);
for (int i = 1; i <= m; ++i)
if (!tr[i]) g.push_back({l[i],r[i]});
memset(vis, 0, sizeof vis);
for (auto it: g){
if (vis[it.first] == 0) {
vis[it.first] = 1;
a.push_back(it.first);
}
if (vis[it.second] == 0){
vis[it.second] = 1;
a.push_back(it.second);
}
}
k = (int)a.size();
get(k);
int all = (1 << (int)g.size()),ans = 0;
for (int i = 1; i < all; ++i)
if (solve(i)) ans++;
printf("%d\n",ans);
return 0;
}
/*
7 8
1 2
1 3
2 4
2 5
3 6
3 7
4 5
3 5
*/