题目:http://acm.hdu.edu.cn/showproblem.php?pid=5379
题意:给出一棵n个节点的树,你在上面放n个麻将,麻将可以从小到大排序编号1-n,要使得:1)每个节点上只有一个麻将,2)父节点相同的节点上麻将的编号连续,3)一棵子树(包括根节点)上的全部麻将,编号为连续,求方案数。
思路:
1)确定了父节点上麻将的编号后,如果子节点有非叶节点,非叶节点的数量不能超过2,否则不能同时满足条件(2)(3);
2)dp[fa] 记录确定 fa 点放的麻将编号后以 fa 为根的树的方案数,计算 dp[fa] 必然需要用1 * dp[son1] * dp[son2] * ...
3)fa 如果有非叶节点,非叶节点为根的大小为b子树,取的编号必然是可取的a个连续编号的左边b个或右边b个,方案数*2;
4)fa 如果有叶节点c个,方案数 * c 的全排列
时间复杂度是n,但是会爆栈,要加 #pragma comment(linker, "/STACK:102400000,102400000") C++交
代码:
#pragma comment(linker, "/STACK:102400000,102400000")
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <algorithm>
#include <vector>
using namespace std;
typedef long long ll;
const int MOD = 1000000007;
int n, flag;
ll A[100005];
vector<int> G[100005];
ll dfs(int fa, int u)
{
if(!flag) return 0;
ll res = 1;
int cnt = 0, cntleaf = 0; // cnt为子节点个数,cntleaf为叶节点个数
for(int i = 0; i < G[u].size(); i++)
{
int v = G[u][i];
if(v != fa)
{
ll temp = dfs(u, v);
if(!flag) return 0;
cnt++;
if(G[v].size() == 1) cntleaf++;
res = res * temp % MOD;
}
}
if(cnt - cntleaf > 2)
{ //非叶节点数大于2
flag = 0; return 0;
}
if(cntleaf)
res = res * A[cntleaf] % MOD;
if(cnt - cntleaf) //有非叶节点
res = res * 2 % MOD;
return res;
}
int main()
{
#ifdef LOCAL
freopen("data.in", "r", stdin);
#endif
A[0] = 1; A[1] = 1;
for(int i = 2; i <= 100000; i++)
A[i] = A[i - 1] * i % MOD;
int t, a, b;
scanf("%d", &t);
for(int cas = 1; cas <= t; cas++)
{
scanf("%d", &n);
if(n == 1)
{
printf("Case #%d: 1\n", cas);
continue;
}
for(int i = 0; i <= n; i++)
G[i].clear();
for(int i = 1; i < n; i++)
{
scanf("%d%d", &a, &b);
G[a].push_back(b);
G[b].push_back(a);
}
flag = 1;
ll ans = dfs(-1, 1);
if(flag)
printf("Case #%d: %I64d\n", cas, ans * 2 % MOD); //节点1放的麻将编号取最大或最小值
else
printf("Case #%d: 0\n", cas);
}
return 0;
}