題目意思:
給你一個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
*/