首页 > 技术文章 > 【HDOJ 5379】 Mahjong tree

lytwajue 2017-08-01 12:04 原文

【HDOJ 5379】 Mahjong tree

往一颗树上标号 要求同一父亲节点的节点们标号连续 同一子树的节点们标号连续 问一共同拥有几种标法

画了一画 发现标号有二叉树的感觉
初始标号1~n 根结点1能够标1或n 否则其它情况无法让以下的子树满足各自连续而且该根的儿子节点都要连续
根结点下的节点平分其它标号 画一画能够发现 每一个根下最多有两颗子树 否则无法满足条件 而且两颗子树占领剩余标号的左右两边 中间夹的必须是叶子 这样才干满足该根下的儿子节点标号连续
若根下仅仅有一颗子树 相同能够选择占剩余标号左部分/右部分
剩余叶子全排列乘上就可以 每一个根都这样遍历一遍 假设期间出现一个根下有两颗以上的子树 就没法标号 即方案数为0 否则遍历完输出方案数就可以

代码例如以下:

#include <iostream>
#include <cmath>
#include <queue>
#include <cstdio>
#include <cstring>
#include <vector>
#define ll long long
#define MOD 1000000007

using namespace std;

ll a[100010]={1,1,2};//A(n,n)排列组合
int n;
vector<int> s[100010];
bool vis[100010];

ll bfs()
{
    memset(vis,0,sizeof(vis));
    queue <int> q;
    q.push(1);
    vis[1] = 1;
    ll ans = 2;
    int i,u,v,yz,gen,sz;
    while(!q.empty())
    {
        u = q.front();
        q.pop();
        yz = gen = 0;
        sz = s[u].size();
        for(i = 0; i < sz; ++i)
        {
            v = s[u][i];
            if(vis[v]) continue;
            if(s[v].size() == 1)//当前节点为叶子节点(仅仅有v-u一条边)
            {
                yz++;
            }
            else//为根结点
            {
                gen++;
                q.push(v);
            }
            vis[v] = 1;
        }
        if(gen > 2) return 0;//根结点超2 无解
        else if(gen)
        {
            ans = ((ans*2)%MOD*a[yz])%MOD;
        }
        else ans = (ans*a[yz])%MOD;
    }
    return ans;
}

int main()
{
    for(int i=3;i<=100001;i++)
        a[i]=(a[i-1]*i)%MOD;
    int t,k=0;
    scanf("%d",&t);
    while(k++,t--)
    {
        scanf("%d",&n);
        memset(s,0,sizeof(s));
        int u,v;
        for(int i=1;i<n;i++)//双向建树
        {
            scanf("%d %d",&u,&v);
            s[u].push_back(v);
            s[v].push_back(u);
        }
        if(n == 1) printf("Case #%d: 1\n",k);//特判仅仅有树根的情况
        else printf("Case #%d: %I64d\n",k,bfs());
    }

    return 0;
}

推荐阅读