【ssoj1027】树形图计数

2407: 树形图计数

时间限制: 1 Sec   内存限制: 128 MB

题目描述

小 k 同学最近正在研究最小树形图问题。所谓树形图,是指有向图的一棵有根的生成树,其中树的每一条边的指向恰好都是从根指向叶结点的方向。现在小 k在纸上画了一个图,他想让你帮忙数一下这个图有多少棵树形图

输入

第1行输入 1个正整数n,表示图中点的个数 
第2到第n+1行每行输入n个字符,描述了这个图的邻接矩阵。 
第i+1行第j个字符如果是0则表示没有从i连向j的有向边,1表示有一条从i到j的有向边。

输出

输出1行1个整数,表示这个有向图的树形图个数

样例输入

4
0100
0010
0001
1000

样例输出

4

提示 对于100%的数据,n≤ 8.


题意就是给你若干条有向边,求有多少种不同的生成树。

因为题目数据规模太水,所以可以直接暴力,即枚举树上每一个点的父亲,判环可以用并查集暴力。

但因为根节点没有父亲,所以需要提前枚举。

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
using namespace std;

#define MAXN 8
#define MAXM
#define INF 0x3f3f3f3f
typedef long long int LL;

bool G[MAXN+5][MAXN+5];
int N;
int fa[MAXN+5];

int root(int x){return fa[x]==x?x:root(fa[x]);}

int dfs(int &rt,int u)//此处是以rt为根,枚举u和谁相连(其中树中边从叶节点向根指)
{
    if(rt==u)return dfs(rt,u+1);
    if(u>N)return 1;

    int rn=0;
    for(int v=1;v<=N;++v)
        if(G[v][u]&&root(v)!=root(u))
        {
            fa[u]=v;
            rn+=dfs(rt,u+1);
            fa[u]=u;
        }

    return rn;
}

int main()
{
    scanf("%d",&N);
    int i,j;

    char s[MAXN+5];
    for(i=1;i<=N;++i)
    {
        scanf("%s",s+1);
        for(j=1;j<=N;++j)
            G[i][j]=s[j]-'0';

        fa[i]=i;
    }

    int ans=0;
    for(i=1;i<=N;++i)
        ans+=dfs(i,1);

    printf("%d\n",ans);
}

除此之外,这道题还可以用状压DP解决。
dp[i][s]表示以i为根,集合中状态为s的构造方案数。
dp[i][s]=dp[i][s0]*dp[j][s^s0]*连边的方案数
但因为dp[i][s]会在转移中重复计算 节点数-1 次,(同一种连边方式可能由不同的节点作为根转移过来)
所以算出dp[i][s]后要除以 节点数-1 。

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
using namespace std;

#define MAXN 8
#define MAXS 256
#define INF 0x3f3f3f3f
typedef long long int LL;

bool G[MAXN+5][MAXN+5];
int N;
int dp[MAXN+10][MAXS+100];

int main()
{
    scanf("%d",&N);
    int i,j;

    char str[MAXN+5];
    for(i=1;i<=N;++i)
    {
        scanf("%s",str+1);
        for(j=1;j<=N;++j)
            G[i][j]=str[j]-'0';
    }

    for(i=1;i<=N;++i)
        dp[i][1<<(i-1)]=1;

    int M=(1<<N)-1;
    for(int s=1;s<=M;++s)
        for(i=1;i<=N;++i)if(s&(1<<(i-1)))
        {
            for(int s0=1;s0<s;++s0)if((s0|s)==s)
                for(j=1;j<=N;++j)if(i!=j&&((1<<(j-1))&(s^s0)))
                {
                    int cnt=0;
                    for(int k=1;k<=N;++k)
                        if(G[k][j]&&(s0&(1<<(k-1))))++cnt;
                    dp[i][s]+=cnt*dp[i][s0]*dp[j][s^s0];
                }

            int cnt=0;
            for(j=1;j<=N;j++)
                if(s&(1<<(j-1)))++cnt;

            if(cnt-1)dp[i][s]/=cnt-1;
        }


    int ans=0;
    for(i=1;i<=N;++i)
        ans+=dp[i][M];

    printf("%d\n",ans);
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值