【问题描述】
小k同学最近正在研究最小树形图问题。所谓树形图,是指有向图的一棵有根的生成树,其中树的每一条边的指向恰好都是从根指向叶结点的方向。现在小k在纸上画了一个图,他想让你帮忙数一下这个图有多少棵树形图,树形图必须包括所有点。
【输入格式】
第1行输入1个正整数:n,表示图中点的个数
第2~n+1行每行输入n个字符,描述了这个图的邻接矩阵。第i+1行第j个字符如果是0则表示没有从i连向j的有向边,1表示有一条从i到j的有向边。
【输出格式】
输出1行1个整数,表示这个有向图的树形图个数。
【输入输出样例】
count.in
4
0100
0010
0001
1000
count.out
4
【数据范围】
对于50%的数据,n<=4。
对于100%的数据,n<=8。
【分析】
- 首先看到数据范围 n≤8 想到搜索
- 因为子节点有多个,每次搜索子节点显然是很难实现的,我们考虑搜索每个节点的父节点
- 若节点
y
能够成为节点
x 的父节点,它必然要满足两个条件:
[1]、存在边 y→x
[2]、当前 y 不为x 的子孙 - 对于条件[1],我们可以直接反着建边( x→y ),则搜索时就能很方便地访问到节点 y
- 对于条件[2],我们记
sum[x] 表示一个二进制数,若第 i 位上为1,i 为 x 的子孙,则y 就要满足 sum[x] & 2y=0 。 - 那么当我们在当前的树上连边
y→x
时,
x
及
x 的子孙都要算入 y 及y 的祖先的 sum 中,同理:回溯时, x 及x 的子孙都要从 y 及y 的祖先的 sum 中扣除,两者可用位运算中的或和异或实现。
【代码】
#include <iostream>
#include <cstdio>
using namespace std;
const int N = 10, M = 100;
int fa[N], sum[N], n, x, Ans;
struct Edge
{
int to; Edge *nxt;
}p[M], *T = p, *lst[N];
inline void addEdge(const int &x, const int &y)
{
(++T)->nxt = lst[x]; lst[x] = T; T->to = y;
}
inline void Dfs(const int &x, const int &rt)
{
int y, w;
if (x == rt) Dfs(x + 1, rt);
if (x > n) return (void)(++Ans);
for (Edge *e = lst[x]; e; e = e->nxt)
{
y = e->to;
if (sum[x] & (1 << y)) continue;
fa[x] = w = y;
int tmp = sum[x] | (1 << x);
while (w) sum[w] |= tmp, w = fa[w];
Dfs(x + 1, rt);
fa[x] = 0; w = y;
while (w) sum[w] ^= tmp, w = fa[w];
}
}
int main()
{
freopen("count.in", "r", stdin);
freopen("count.out", "w", stdout);
scanf("%d", &n);
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= n; ++j)
{
scanf("%1d", &x);
if (x) addEdge(j, i);
}
for (int i = 1; i <= n; ++i) Dfs(1, i);
printf("%d\n", Ans);
fclose(stdin); fclose(stdout);
return 0;
}