有向Cactus图:
1.它是一个强连通图。
2.它的任意一条边都属于且仅属于一个环。
有向Cactus图判定:
性质1 有向Cactus的DFS树没有横向边(不等价于非父子边)。
性质2 low(u)<=dfn(v) (u是v的儿子)
性质3 设某个点v有a(v)个儿子的low值小于dfn(v),同时v自己有b(v)条逆向边。那么a(v)+b(v)<2。
这三条性质也就是一个有向图是有向Cactus的充要条件。详细的证明请看《cactus solution》写的很详细,三个条件都有。
对应的题目HDU 3594,只要搞懂了定理很好实现,通过此题深刻理解了横叉边和反向边的区别。
无向Cactus图定义:
1.它是一个连通图。
2.它的任意一条边都至多属于一个环。
poj 2793 Cactus
题意:判断一个图是否为cactus图,并求计算一个图的cactus度:有多少个生成子图(包括自身)也是cactus。
解法:根据无向仙人掌图的定义,只需判断每条边属于几个环即可,对图进行dfs后所有父子边形成以棵树,每条反向边<a,b>加入树后都会使a-lca[a][b]--b形成一个环,可以用poj3417的做法(详见《Tarjan离线算法求LCA小结》)统计每条边被环覆盖了多少次。还有一种方法是在dfs时顺便维护,实现起来可能还会更简单些。
在确定是cactus图后只需统计每个环上有多少条边,然后利用乘法原理就可以计算度数.由于每条反向边只会形成一个环,因此记录每个节点的深度depth[],在碰到反向边<b,a>时,depth[b]-depth[a]+1就是这个环内边的条数,累乘即可,注意要用高精。
public class Main{
int maxn = 20010, maxm = 1000010;
class node {
int be, ne, val;
node(int b, int e, int v) {
be = b;
ne = e;
val = v;
}
}
class LCA {
node buf[] = new node[maxn * 2], query[] = new node[maxn * 2];
int Eb[] = new int[maxn], lb, Eq[] = new int[maxn], lq;
void init(int n) {
lq = lb = 0;
for (int i = 1; i <= n; i++) {
f[i] = i;
Eb[i] = Eq[i] = -1;
vis[i] = 0;
}
}
void addedge(int a, int b, int v) {
buf[lb] = new node(b, Eb[a], v);
Eb[a] = lb++;
buf[lb] = new node(a, Eb[b], v);
Eb[b] = lb++;
}
void addquery(int a, int b, int v) {
query[lq] = new node(b, Eq[a], v);
Eq[a] = lq++;
query[lq] = new node(a, Eq[b], v);
Eq[b] = lq++;
}
int f[] = new int[maxn], vis[] = new int[maxn];
int find(int x) {
if (x != f[x])
f[x] = find(f[x]);
return f[x];
}
void dfs(int a) {
vis[a] = 1;
// 处理子树
for (int i = Eq[a]; i != -1; i = query[i].ne) {
int b = query[i].be;
if (vis[b] == 1) {
int temp = find(b);
ans[temp] -= 2;
//System.out.println("te "+temp);
}
}
for (int i = Eb[a]; i != -1; i = buf[i].ne) {
int b = buf[i].be;
if (vis[b] == 0) {
dfs(b);
f[b] = a;
ans[a] += ans[b];
// System.out.println(a+" "+b+" "+ans[a]+" "+ans[b]);
}
}
}
}
class BCC {
node buf[] = new node[maxm*2];
int E[] = new int[maxn], len, n;
int dfn[] = new int[maxn], low[] = new int[maxn],cnt;
int depth[]=new int[maxn];
void init(int n) {
this.n = n;
Arrays.fill(E, -1);
len = 0;
}
void add(int a, int b) {
buf[len] = new node(b, E[a], 0);
E[a] = len++;
buf[len] = new node(a, E[b], 0);
E[b] = len++;
}
void dfs(int a, int fa,int d) {
//System.out.println(a);
dfn[a] = low[a] = ++cnt;
depth[a]=d;
for (int i = E[a]; i != -1; i = buf[i].ne) {
int b = buf[i].be;
if (dfn[b] == 0) {
dfs(b, a,d+1);
lca.addedge(a, b,i);
low[a] = Math.min(low[b], low[a]);
} else if (b != fa && dfn[b] < low[a]) {
low[a] = dfn[b];
//System.out.println(a+" "+b);
res=res.multiply(new BigInteger(depth[a]-depth[b]+2+""));
ans[a]++;
ans[b]++;
lca.addquery(a, b, i);
}
}
}
String solve() {
for (int i = 1; i <= n; i++)
dfn[i] = low[i] = 0;
cnt = 0;
Arrays.fill(ans, 0);
dfs(1,1,1);
for (int i = 1; i <= n; i++)
if (dfn[i] == 0)
return "0";
lca.dfs(1);
for(int i=1;i<=n;i++)
if(ans[i]>1)
return "0";
return res.toString();
}
}
int ans[] = new int[maxn];
LCA lca = new LCA();
BCC bcc = new BCC();
int path[] = new int[1010];
boolean vis[] = new boolean[maxn];
BigInteger res;
StreamTokenizer in = new StreamTokenizer(new BufferedReader(
new InputStreamReader(System.in)));
int nextInt() throws IOException {
in.nextToken();
return (int) in.nval;
}
void run() throws IOException {
int n = nextInt();
int m = nextInt();
res=BigInteger.ONE;
bcc.init(n);
lca.init(n);
while (m-- > 0) {
int k = nextInt();
for (int i = 1; i <= k; i++)
path[i] = nextInt();
for (int i = 1; i < k; i++)
bcc.add(path[i], path[i + 1]);
}
System.out.println(bcc.solve());
}
public static void main(String[] args) throws IOException {
new Main().run();
}
}
关于cactus 问题,周冬08年的论文里有详细的介绍,主要是利用dfs树性质结合cactus图双连通、强连通的特殊性进行求解,例如求cactus直径/最长链问题等。