[日常训练] 树形图计数

【问题描述】

小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。

【分析】
  • 首先看到数据范围 n8 想到搜索
  • 因为子节点有多个,每次搜索子节点显然是很难实现的,我们考虑搜索每个节点的父节点
  • 若节点 y 能够成为节点x的父节点,它必然要满足两个条件:
    [1]、存在边 yx
    [2]、当前 y 不为x的子孙
  • 对于条件[1],我们可以直接反着建边( xy ),则搜索时就能很方便地访问到节点 y
  • 对于条件[2],我们记sum[x]表示一个二进制数,若第 i 位上为1,i x 的子孙,则y就要满足 sum[x] & 2y=0
  • 那么当我们在当前的树上连边 yx 时, 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;
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值