bzoj1005

1005: [HNOI2008]明明的烦恼

Time Limit: 1 Sec  Memory Limit: 162 MB
Submit: 3032  Solved: 1209

Description

自从明明学了树的结构,就对奇怪的树产生了兴趣...... 给出标号为1到N的点,以及某些点最终的度数,允许在任意两点间连线,可产生多少棵度数满足要求的树?

Input

第一行为N(0 < N < = 1000),接下来N行,第i+1行给出第i个节点的度数Di,如果对度数不要求,则输入-1

Output

一个整数,表示不同的满足要求的树的个数,无解输出0

Sample Input

3
1
-1
-1

Sample Output

2

HINT

两棵树分别为1-2-3;1-3-2

Source

大意:给出n个点的度数限制,问这些点能组成多少棵树

分析:

首先介绍一种数列Purfer Code:

这种数列是这样子的:对于一棵树(这棵树是不存在根的),每次在数列中加入编号最小的孩子节点(即为度数为1的节点)所对应的父亲节点,并在树中删除此点,不断重复此操作,知道这棵树只剩下两个点

如:

1

/     \

2         3

/     \

4        5

所对应的PurferCode就是:1 2 2

因为第一次删掉3,第二次删掉1,第三次删掉4

剩下2、5两个节点

可以发现,每个PurferCode对应的树是唯一的

我们也可以从PurferCode得到一棵树

首先由PurferCode计算出每点的度数(出现的次数+1),

然后每次(第 i 次)选择度数最少(度数大于1)的点(度数相同时编号较小的优先)与数列第 i 个点连边,然后这两个点的度数都-1,

这样不断操作,会最后的到两个度数为1的点,将这两点相连,即的原来的树

设度数有限制的点的个数为Cnt,度数-1之和为Sum(即为数列中这些点要占的个数),第 i 个点的度数为di

那么可以根据组合数学:

从数列n-2个点中选择Sum个点有C(Sum, n-2)

这Sum个点中组合C(d1-1, Sum)*C(d2-1, Sum-(d1-1))*C(d3-1, Sum-(d1-1)-(d2-1))*........*C(dn - 1, dn - 1)

剩下的n-2-Sum个位置由其余n-Cnt个点组合,有(n-Cnt)^(n-2-Sum)种方案

所以Ans = C(Sum, n-2)*C(d1-1, Sum)*C(d2-1, Sum-(d1-1))*C(d3-1, Sum-(d1-1)-(d2-1))*........*C(dn - 1, dn - 1)*(n-Cnt)^(n-2-Sum)

因为C(Sum, n-2) = (n-2)!/(Sum!*(n-2-Sum)!),

C(d1-1, Sum) = Sum!/((d1-1)!*(Sum-(d1-1))!

C(d2-1, Sum-(d1-1)) = (Sum-(d1-1))!/((d2-1)!*(Sum-(d1-1)-(d2-1))!)

.........

相乘时加粗部分可以相消

最终可得:

Ans =  [ (n-2)!*((n-Cnt)^(n-2-Sum)) ]  /    [(n-2-Sum)!*(d1-1)!*(d2-1)!*.....*(dn-1)!]

写程序时,乘除都会很大,高精度除法和高精度乘高精度都很麻烦,所以可以分解质因数,最终可以只写单精度乘高精度即可。

关于分解x!的质因数

x = p^a   *    k(即x可以整除p^a)

a = x div p  +  x div p^2 + x div p^3 + ..... + x div p^m (p^m为p的阶乘中小于x的最大阶乘,即:  p^m <= x,  p^(m+1) > x)

那么 x!= p1^x1 * p2^x2  *  p3^x3 * ...... * pn^xn

这个是显然的,大家写写就知道

关于分解质因数,可以用O(n)筛素数,这个不讲

综上所述,本题得解

  1 #include <cstdio>
  2 #include <cstring>
  3 #include <cstdlib>
  4 #include <cmath>
  5 #include <deque>
  6 #include <vector>
  7 #include <queue>
  8 #include <iostream>
  9 #include <algorithm>
 10 #include <map>
 11 #include <set>
 12 #include <ctime>
 13 using namespace std;
 14 typedef long long LL;
 15 #define For(i, s, t) for(int i = (s); i <= (t); i++)
 16 #define Ford(i, s, t) for(int i = (s); i >= (t); i--)
 17 #define MIT (2147483647)
 18 #define INF (1000000001)
 19 #define MLL (1000000000000000001LL)
 20 #define sz(x) ((bnt) (x).size())
 21 #define clr(x, y) memset(x, y, sizeof(x))
 22 #define puf push_front
 23 #define pub push_back
 24 #define pof pop_front
 25 #define pob pop_back
 26 #define ft first
 27 #define sd second
 28 #define mk make_pair
 29 inline void SetIO(string Name) {
 30     string Input = Name+".in",
 31     Output = Name+".out";
 32     freopen(Input.c_str(), "r", stdin),
 33     freopen(Output.c_str(), "w", stdout);
 34 }
 35
 36 const int N = 1010, LEN = 1010, M = 10000;
 37 int Prime[N], p;
 38 bool Visit[N];
 39 int n, D[N], Cnt, Sum;
 40 int P1[N], P2[N];
 41 int Ans[LEN], Len;
 42
 43 inline void Input() {
 44     scanf("%d", &n);
 45     For(i, 1, n) scanf("%d", &D[i]);
 46 }
 47
 48 inline void EXIT(int Ans) {
 49     printf("%d\n", Ans);
 50     exit(0);
 51 }
 52
 53 inline void BuildPrime() {
 54     clr(Visit, 0), p = 0;
 55     For(i, 2, n){
 56         if(!Visit[i]) Prime[++p] = i;
 57         For(j, 1, p) {
 58             if(i*Prime[j] > n) break;
 59             Visit[i*Prime[j]] = 1;
 60             if(i%Prime[j] == 0) break;
 61         }
 62     }
 63 }
 64
 65 inline void FenJie(int A, int *P) {
 66     For(i, 1, p) {
 67         int x = Prime[i];
 68         while(x <= A) {
 69             P[i] += A/x;
 70             x *= Prime[i];
 71         }
 72     }
 73 }
 74
 75 inline void Mul(int x) {
 76     For(i, 1, Len) Ans[i] *= x;
 77     For(i, 1, Len) {
 78         Ans[i+1] += Ans[i]/M;
 79         Ans[i] %= M;
 80     }
 81     while(Ans[Len+1]) {
 82         Len++;
 83         Ans[Len+1] += Ans[Len]/M;
 84         Ans[Len] %= M;
 85     }
 86 }
 87
 88 inline void Solve() {
 89     //Check
 90     if(n == 1) {
 91         if(D[1] == 0 || D[1] == -1) EXIT(1);
 92         EXIT(0);
 93     }
 94     For(i, 1, n)
 95         if(D[i] == 0) EXIT(0);
 96     Cnt = Sum = 0;
 97     For(i, 1, n)
 98         if(D[i] > 0) Cnt++, Sum += D[i]-1;
 99     if(Sum > n-2 || (Sum < n-2 && n == Cnt)) EXIT(0);
100
101     //Work
102     BuildPrime();
103     clr(P1, 0), clr(P2, 0);
104     FenJie(n-2, P1);
105     FenJie(n-2-Sum, P2);
106     For(i, 1, n)
107         if(D[i] > 1) FenJie(D[i]-1, P2);
108     int m = n-Cnt;
109     For(i, 1, p) {
110         if(m <= 1) break;
111         int t = 0, k = Prime[i];
112         while(!(m%k)) m /= k, t++;
113         P1[i] += t*(n-2-Sum);
114     }
115     For(i, 1, p) P1[i] -= P2[i];
116
117     Ans[1] = 1, Len = 1;
118     For(i, 1, p)
119         For(j, 1, P1[i]) Mul(Prime[i]);
120
121     printf("%d", Ans[Len]);
122     Ford(i, Len-1, 1) printf("%04d", Ans[i]);
123     cout<<endl;
124 }
125
126 int main() {
127     SetIO("1005");
128     Input();
129     Solve();
130     return 0;
131 }

下面聊聊用线性筛素数法求欧拉函数

欧拉函数有两个定理

1、当m,n互质时,phi(m*n) = phi(m)*phi(n);

2、phi(p^k) = (p-1)*p^(k-1)

因为1-p^k间只有p的倍数不与其互质,这样的数有p^k/p个,即p^(k-1)个,即phi(p^k) = p^k - p^(k-1) = (p-1)*p^(k-1)

那么当n%p == 0 (显然不互质,p为质数)时,

设n = m * p^k(m与p互质)

phi(n) = phi(m)*phi(p^k) = phi(m)*(p-1)*p^(k-1)

phi(n*p) = phi(m*p^(k+1)) = phi(m)*(p-1)*p^k = phi(n)*p

所以可以得求欧拉函数代码

 1 for(int i=2;i<N;i++)  {
 2     if(!vis[i]) {
 3         prime[++cnt]=i;
 4         phi[i]=i-1;
 5     }
 6     for(int k=1;k<=cnt&&prime[k]*i<N;k++) {
 7         x=prime[k]*i;
 8         vis[x]=true;
 9         if(i%prime[k]==0) {
10             phi[x]=phi[i]*prime[k];
11             break;
12         }
13         else phi[x]=phi[i]*(prime[k]-1);
14     }
15 }

时间: 2024-11-10 03:40:54

bzoj1005的相关文章

BZOJ1005: [HNOI2008]明明的烦恼

Description 自从明明学了树的结构,就对奇怪的树产生了兴趣...... 给出标号为1到N的点,以及某些点最终的度数,允许在任意两点间连线,可产生多少棵度数满足要求的树? Input 第一行为N(0 < N < = 1000),接下来N行,第i+1行给出第i个节点的度数Di,如果对度数不要求,则输入-1 Output 一个整数,表示不同的满足要求的树的个数,无解输出0 Sample Input 31-1-1 Sample Output 2 HINT 两棵树分别为1-2-3;1-3-2

[BZOJ1005]Prufer数列+排列组合

一棵树的Prufer数列 每次在剩下的树中找到标号最小的叶子节点(对于无根树而言即是度数为1的节点),删去. 同时将其父节点(即与其相连的唯一点)加入Prufer数列当中. 一个Prufer数列所对应的树 G集合开始为空集 设当前处理到Prufer数列的第i项,找到G集合中未出现且在Prufer[i..n-2]未出现过的标号最小的节点,设其为u. 将u加入集合G中,并将u与Prufer[i]连一条边. 最后将在G集合中仍未出现的两个点之间连一条边(其中必定有一个是n). 来思考一下为何可以还原成

【BZOJ1005/1211】[HNOI2008]明明的烦恼/[HNOI2004]树的计数 Prufer序列+高精度

[BZOJ1005][HNOI2008]明明的烦恼 Description 自从明明学了树的结构,就对奇怪的树产生了兴趣......给出标号为1到N的点,以及某些点最终的度数,允许在任意两点间连线,可产生多少棵度数满足要求的树? Input 第一行为N(0 < N < = 1000),接下来N行,第i+1行给出第i个节点的度数Di,如果对度数不要求,则输入-1 Output 一个整数,表示不同的满足要求的树的个数,无解输出0 Sample Input 3 1 -1 -1 Sample Outp

[bzoj1005] [JSOI2008]星球大战starwar

想了半天决定第一篇就发一道水题的题解吧……这道题以前就看了但是神奇的第一次没有写对,然后今天写代码+调试,额……二十分钟…… 题目懒得贴了,直接戳这里 题目大意就是给你一个图(毫无疑问稀疏图),然后每一次抹去其中一个节点,然后求这时的连通块个数. 第一次看这题的时候在学tarjan,第一反应就是每次把点去掉跑tarjan,然后看了下数据范围就放弃了[其实也能过几个点对不对233] 然后就想到用并查集,把所有的操作全部用一个数组存下来,然后倒着合并. 首先将所有要消除的点全部抹去,用并查集求出当前

【prufer编码+组合数学】BZOJ1005 [HNOI2008]明明的烦恼

Description 自从明明学了树的结构,就对奇怪的树产生了兴趣...... 给出标号为1到N的点,以及某些点最终的度数,允许在任意两点间连线,可产生多少棵度数满足要求的树? Solution 这道题就是树的计数加强版,多了不要求的情况. 对于已限制的情况,就是C(n-2,t)*可重复元素的公式,考虑其他不限制的元素,再*(n-t)^(n-2-sum),t为已限制点个数,sum为已限制度数. 大概就是这个意思,计算要用分解质因数+高精度,具体细节自己推一推. Code 因为是高精乘低精,高精

【BZOJ1005】【HNOI2008】明明的烦恼

又是看黄学长的代码写的,估计我的整个BZOJ平推计划都要看黄学长的代码写 原题: 自从明明学了树的结构,就对奇怪的树产生了兴趣......给出标号为1到N的点,以及某些点最终的度数,允许在任意两点间连线,可产生多少棵度数满足要求的树? 0 < N < = 1000 这题用到了树的prufer编码 prufer编码是什么呐 注意下面扯到的树都是无根树(下面不少定义是从黄学长哪里直接粘过来的) 每次删除树中度数为1且序号最小的节点,并在序列中添加与其相邻的节点的序号,直到树中有两个节点,手玩一组小

[bzoj1005][HNOI2008][明明的烦恼] (高精度+prufer定理)

Description 自从明明学了树的结构,就对奇怪的树产生了兴趣......给出标号为1到N的点,以及某些点最终的度数,允许在任意两点间连线,可产生多少棵度数满足要求的树? Input 第一行为N(0 < N < = 1000),接下来N行,第i+1行给出第i个节点的度数Di,如果对度数不要求,则输入-1 Output 一个整数,表示不同的满足要求的树的个数,无解输出0 Sample Input 3 1 -1 -1 Sample Output 2 HINT 两棵树分别为1-2-3;1-3-

[BZOJ1005](HNOI 2008)明明的烦恼

Description 自从明明学了树的结构,就对奇怪的树产生了兴趣...... 给出标号为1到N的点,以及某些点最终的度数,允许在任意两点间连线,可产生多少棵度数满足要求的树? Input 第一行为N(0 < N < = 1000),接下来N行,第i+1行给出第i个节点的度数Di,如果对度数不要求,则输入-1 Output 一个整数,表示不同的满足要求的树的个数,无解输出0 Sample Input 3 1 -1 -1 Sample Output 2 HINT 两棵树分别为1-2-3;1-3

【bzoj1005】[HNOI2008]明明的烦恼 Prufer序列+高精度

题目描述 给出标号为1到N的点,以及某些点最终的度数,允许在任意两点间连线,可产生多少棵度数满足要求的树? 输入 第一行为N(0 < N < = 1000),接下来N行,第i+1行给出第i个节点的度数Di,如果对度数不要求,则输入-1 输出 一个整数,表示不同的满足要求的树的个数,无解输出0 样例输入 3 1 -1 -1 样例输出 2 题解 Prufer序列+高精度 Prufer序列:由一棵 $n$ 个点的树唯一产生的一个 $n-2$ 个数的序列. 生成方法:找到这棵树编号最小的叶子节点,将其