UVA 11174 Stand in a Line,UVA 1436 Counting heaps —— (组合数的好题)
这两个题的模型是有n个人,有若干的关系表示谁是谁的父亲,让他们进行排队,且父亲必须排在儿子前面(不一定相邻)。求排列数。
我们假设s[i]是i这个节点,他们一家子的总个数(或者换句话说,等于他的子孙数+1(1是他本身)),f[i]是以i为根的节点的排列种数。那么总的种数为n!/(s[1]+s[2]+...+s[n])。关于这个递推式的得出,是根据排列公式的,我们假设一个例子,不妨设4.5是2的子孙,3是1的子孙。那么他们进行排队的话,不妨看成222和11排队就是2的家族和1的家族排队(2和1是平行关系)。那么他们如果真的是全部一样的话,排列数为5!/(2!*3!)。然后再根据他们自身有的排列种数,不妨设为f[2],f[1],那么总的排列数为他们相乘,即:f[2]*f[1]*5!/(2!*3!)。因此,推广开来对于一个节点i(这里看做是2,1的父亲),那么以节点i为根的子节点的排列数为:f[i]=f[c1]*f[c2]*...*d[ck]*(s[i]-1)!/(s[c1]!*s[c2]!*...*s[ck]!),其中这c1到ck代表这他的若干个儿子,如果将每个f都进行同样的递推,一直到这个节点是一个点为止,这样可以消去所有的f(因为一个点的排序种数就是1),这样子的话,可以化成f[i]=(s[i]-1)!/(s[1]+s[2]+...+s[ck]),对所有点,用一个虚拟节点0当做父亲,这样,得到f[0]=(s[0]-1)!/(s[1]+s[2]+...+s[n])。考虑到s[0]=n+1,因此得到上面的公式。
得到这个公式以后,下面的代码都顺理成章了。第一题用的是求逆元,因为mod的是一个素数,而第二题不是,所以用的是分解质因数。第二题是一个很好的题,涉及到了很多知识点,还包括了非递归的求一棵树的每个节点的包含的子节点的个数的方法(即上面的s,当然,前面的说法不准确,加上1才是上面的s)。
具体见代码:
#include <stdio.h>
#include <algorithm>
#include <string.h>
#include <iostream>
#include <vector>
using namespace std;
const int mod = ;
typedef long long ll;
const int N = +; vector<int> G[N];
int n,m,s[N];
int in[N];
ll fac[N],inv[N]; ll qpow(ll x,ll n)
{
ll ans = ;
while(n)
{
if(n&) ans = (ans*x) % mod;
n >>= ;
x = (x*x) % mod;
}
return ans;
} void init()
{
for(int i=;i<=n;i++) G[i].clear();
memset(in,,sizeof(in));
memset(s,,sizeof(s));
} void get()
{
fac[]=inv[]=;
for(int i=;i<=;i++)
{
fac[i] = (fac[i-]*i)%mod;
inv[i] = qpow(i,mod-);
}
} int dfs(int x)
{
int& ans = s[x];
ans = ;
for(int i=;i<G[x].size();i++)
{
int v = G[x][i];
ans += dfs(v);
}
return ans;
} int main()
{
get();
int T;
scanf("%d",&T);
while(T--)
{
init();
scanf("%d%d",&n,&m);
while(m--)
{
int u,v;
scanf("%d%d",&u,&v);
G[v].push_back(u);
in[u] ++;
}
for(int i=;i<=n;i++)
{
if(!in[i]) dfs(i);
} ll ans = fac[n];
for(int i=;i<=n;i++) ans = (ans*inv[s[i]]) % mod;
cout<<ans<<endl;
}
}
#include <stdio.h>
#include <algorithm>
#include <string.h>
#include <iostream>
#include <vector>
using namespace std;
const int mod = ;
typedef long long ll;
const int N = +; vector<int> G[N];
int n,m,s[N];
bool vis[N];
ll fac[N],inv[N]; ll qpow(ll x,ll n)
{
ll ans = ;
while(n)
{
if(n&) ans = (ans*x) % mod;
n >>= ;
x = (x*x) % mod;
}
return ans;
} void init()
{
for(int i=;i<=n;i++) G[i].clear();
memset(vis,false,sizeof(vis));
memset(s,,sizeof(s));
} void get()
{
fac[]=inv[]=;
for(int i=;i<=;i++)
{
fac[i] = (fac[i-]*i)%mod;
inv[i] = qpow(i,mod-);
}
} int dfs(int x)
{
int& ans = s[x];
if(ans != ) return ans;
ans = ;
vis[x] = true;
for(int i=;i<G[x].size();i++)
{
int v = G[x][i];
ans += dfs(v);
}
return ans;
} int main()
{
get();
int T;
scanf("%d",&T);
while(T--)
{
init();
scanf("%d%d",&n,&m);
while(m--)
{
int u,v;
scanf("%d%d",&u,&v);
G[v].push_back(u);
}
for(int i=;i<=n;i++)
{
if(!vis[i]) dfs(i);
} ll ans = fac[n];
for(int i=;i<=n;i++) ans = (ans*inv[s[i]]) % mod;
cout<<ans<<endl;
}
}
上面两题都是递归求s的方法,第一种是根据入度来求,第二种是根据记忆化搜索来求。
第二题的代码如下,第二题的代码注释很详细,值得好好研究。代码如下:
#include <stdio.h>
#include <algorithm>
#include <string.h>
#include <iostream>
#include <vector>
#include <queue>
using namespace std;
typedef long long ll;
const int N = +; int n,mod;
bool isprime[N];
int prime[N],tot=,fa[N],cnt[N],vis[N]; void getPrimeTable()
{
memset(isprime,true,sizeof(isprime));
for(int i=;i<N;i++)
{
if(isprime[i])
{
prime[++tot] = i;
for(ll j=(ll)i*i;j<(ll)N;j+=i)
{
isprime[j]=false;
}
}
}
} ll qpow(ll x,ll n)
{
ll ans = ;
while(n)
{
if(n&) ans = (ans*x) % mod;
n >>= ;
x = (x*x) % mod;
}
return ans;
} void getNode() // 这里用bfs的方法从子节点到根节点倒序统计出每个节点所包含的子节点的数目
{
memset(cnt,,sizeof(cnt)); // 这里cnt表示的是每个节点所包含的节点的数目
queue<int> Q;
for(int i=;i<=n;i++)
{
if(!vis[i]) Q.push(i); // 如果该节点出度为0,则是叶子节点,加入队列
}
while(!Q.empty())
{
int x = Q.front();Q.pop();
cnt[x] += ; // 该叶子节点本身也算在内
cnt[fa[x]] += cnt[x]; // 父亲节点数增加该节点的节点数
vis[fa[x]] --; // 父亲节点的出度减1
if(!vis[fa[x]]) Q.push(fa[x]);
}
} void init()
{
memset(vis,,sizeof(vis));
for(int i=;i<=n;i++)
{
scanf("%d",fa+i);
vis[fa[i]] ++; // 这里vis表示的是该点的出度
}
getNode(); memset(vis,,sizeof(vis)); // 这里vis重新用于记载某个分母中某个因子存在的个数
for(int i=;i<=n;i++) // 因为s[i]的数目最大不会超过n
vis[cnt[i]] ++;
} void cal(int u,int dt) // 用于分解质因数的计算
{
for(int i=;i<=tot;i++)
{
int k = prime[i];
while(u%k == )
{
cnt[k] += dt;
u /= k;
}
if(isprime[u]) // 如果u已经是质数了,就可以返回了
{
cnt[u] += dt;
return;
}
}
} ll solve()
{
memset(cnt,,sizeof(cnt)); // 这里cnt重新用于记录分数上下约分以后每个质因数的个数
for(int i=;i<=n;i++) cal(i,); // 对n的阶乘的计算
for(int i=;i<=n;i++)
{
if(vis[i]) cal(i,-vis[i]); // 对分母的计算
} ll ans = ;
for(int i=;i<=tot;i++)
{
int k = prime[i];
if(cnt[k]) ans = (ans*qpow(k,(ll)cnt[k])) % mod;
}
return ans;
} int main()
{
getPrimeTable();
int T;
scanf("%d",&T);
while(T--)
{
scanf("%d%d",&n,&mod);
init();
cout<<solve()<<endl;
}
}
另外值得注意的是,线性筛素数的方法。
UVA 11174 Stand in a Line,UVA 1436 Counting heaps —— (组合数的好题)的更多相关文章
- uva 11174 Stand in a Line
// uva 11174 Stand in a Line // // 题目大意: // // 村子有n个村民,有多少种方法,使村民排成一条线 // 使得没有人站在他父亲的前面. // // 解题思路: ...
- UVA 11174 Stand in a Line 树上计数
UVA 11174 考虑每个人(t)的所有子女,在全排列中,t可以和他的任意子女交换位置构成新的排列,所以全排列n!/所有人的子女数连乘 即是答案 当然由于有MOD 要求逆. #include & ...
- uva 11174 Stand in a Line (排列组合)
UVa Online Judge 训练指南的题目. 题意是,给出n个人,以及一些关系,要求对这n个人构成一个排列,其中父亲必须排在儿子的前面.问一共有多少种方式. 做法是,对于每一个父节点,将它的儿子 ...
- UVA 11174 Stand in a Line (组合+除法的求模)
题意:村子里有n个人,给出父亲和儿子的关系,有多少种方式可以把他们排成一列,使得没人会排在他父亲的前面 思路:设f[i]表示以i为根的子树有f[i]种排法,节点i的各个子树的根节点,即它的儿子为c1, ...
- UVA 11174 Stand in a Line 树dp+算
主题链接:点击打开链接 题意:白书的P103. 加个虚根就能够了...然后就是一个多重集排列. import java.io.PrintWriter; import java.util.ArrayLi ...
- 【递推】【推导】【乘法逆元】UVA - 11174 - Stand in a Line
http://blog.csdn.net/u011915301/article/details/43883039 依旧是<训练指南>上的一道例题.书上讲的比较抽象,下面就把解法具体一下.因 ...
- uva 1436 - Counting heaps(算)
题目链接:uva 1436 - Counting heaps 题目大意:给出一个树的形状,如今为这棵树标号,保证根节点的标号值比子节点的标号值大,问有多少种标号树. 解题思路:和村名排队的思路是一仅仅 ...
- 数学:UVAoj 11174 Stand in a Line
Problem J Stand in a Line Input: Standard Input Output: Standard Output All the people in the bytela ...
- CDQ分治入门 + 例题 Arnooks's Defensive Line [Uva live 5871]
CDQ分治入门 简介 CDQ分治是一种特别的分治方法,它由CDQ(陈丹琦)神犇于09国家集训队作业中首次提出,因此得名.CDQ分治属于分治的一种.它一般只能处理非强制在线的问题,除此之外这个算法作为某 ...
随机推荐
- [Vue]method与计算属性computed、侦听器watch与计算属性computed的区别
一.方法method与计算属性computed的区别 方法method:每当触发重新渲染时,调用方法method将总会再次执行函数: 计算属性computed:计算属性computed是基于它们的响应 ...
- Mysql定时备份[Windows]
基于mysql5.6.39版本 一.备份脚本 1.windows环境创建批处理文件 @echo off rem ******MySQL backup start****** set mysqlHome ...
- Android 官方下拉刷新 SwipeRefreshLayout
0.build.gradle compile 'com.android.support:support-v4:23+' 1.布局文件 <android.support.v4.widget.Swi ...
- C# 将一种类型的数组转化为另一种类型的数组
//字符串数组(源数组) "}; //整型数组(目标数组) int[] iNums; //转换方法 iNums = Array.ConvertAll<string, int>(s ...
- Unsupported major.minor version 52.0错误和 jdbc odbc
什么是JDBC? JDBC, 全称为Java DataBase Connectivity standard, 它是一个面向对象的应用程序接口(API), 通过它可访问各类关系数据库.JDBC也是jav ...
- 3. Java开发环境的搭建:安装JDK,配置环境变量
1.安装JDK开发环境 下载网站:http://www.oracle.com/ 开始安装JDK: 修改安装目录如下: 确定之后,单击“下一步”. 注:当提示安装JRE时,可以选择不要安装. 2.配置环 ...
- SpringBoot-JPA入门
SpringBoot-JPA入门 JPA就是Spring集成了hibernate感觉. 注解,方法仓库(顾名思义的方法,封装好了,还有自定义的方法). 案例: spring: datasource: ...
- spark2.0新特性之DataSet
1.Spark SQL,DataFrame,DataSet的错误类型检测时机 spark SQL:其类型检测与语法检测是在运行时检测的 DataFrame:在spark2.0以前的版本中,DataFr ...
- 学习前端第二天之css层叠样式
一.设置样式公式 选择器 {属性:值:} 二.font 设置四大操作 font-size:字体大小 (以像素为单位) font-weight:字体粗细 font-family:字体 ( 可直接跟 ...
- vue覆盖UI组件样式不生效
检查检查是不是加了scoped 在vue中,我们需要引用子组件,包括ui组件(element.iview). 但是在父组件中添加scoped之后,在父组件中书写子组件的样式是无效果的. 去掉scope ...