Aizu - 1388 Problem K Counting Cycles

本文介绍了一种解决Aizu问题1388的方法,即在给定的无向图中求简单环(度数为2的连通子图)的数量。通过分析最多16条额外边的情况,提出利用位运算的状态压缩和树形结构来优化算法。首先,对于小规模的n,使用二进制枚举和判断边是否在环上的技巧。然后,对于大规模的n,引入虚树来减少计算量。注意点包括如何找出非树边以及避免每次枚举都清空图。最后,反思了错误的建图方式和处理树的方法。
摘要由CSDN通过智能技术生成

题目意思:

给你一个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

*/
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值