有向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(); } }