这两个题的模型是有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 —— (组合数的好题)的更多相关文章

  1. uva 11174 Stand in a Line

    // uva 11174 Stand in a Line // // 题目大意: // // 村子有n个村民,有多少种方法,使村民排成一条线 // 使得没有人站在他父亲的前面. // // 解题思路: ...

  2. UVA 11174 Stand in a Line 树上计数

    UVA 11174 考虑每个人(t)的所有子女,在全排列中,t可以和他的任意子女交换位置构成新的排列,所以全排列n!/所有人的子女数连乘   即是答案 当然由于有MOD 要求逆. #include & ...

  3. uva 11174 Stand in a Line (排列组合)

    UVa Online Judge 训练指南的题目. 题意是,给出n个人,以及一些关系,要求对这n个人构成一个排列,其中父亲必须排在儿子的前面.问一共有多少种方式. 做法是,对于每一个父节点,将它的儿子 ...

  4. UVA 11174 Stand in a Line (组合+除法的求模)

    题意:村子里有n个人,给出父亲和儿子的关系,有多少种方式可以把他们排成一列,使得没人会排在他父亲的前面 思路:设f[i]表示以i为根的子树有f[i]种排法,节点i的各个子树的根节点,即它的儿子为c1, ...

  5. UVA 11174 Stand in a Line 树dp+算

    主题链接:点击打开链接 题意:白书的P103. 加个虚根就能够了...然后就是一个多重集排列. import java.io.PrintWriter; import java.util.ArrayLi ...

  6. 【递推】【推导】【乘法逆元】UVA - 11174 - Stand in a Line

    http://blog.csdn.net/u011915301/article/details/43883039 依旧是<训练指南>上的一道例题.书上讲的比较抽象,下面就把解法具体一下.因 ...

  7. uva 1436 - Counting heaps(算)

    题目链接:uva 1436 - Counting heaps 题目大意:给出一个树的形状,如今为这棵树标号,保证根节点的标号值比子节点的标号值大,问有多少种标号树. 解题思路:和村名排队的思路是一仅仅 ...

  8. 数学:UVAoj 11174 Stand in a Line

    Problem J Stand in a Line Input: Standard Input Output: Standard Output All the people in the bytela ...

  9. CDQ分治入门 + 例题 Arnooks's Defensive Line [Uva live 5871]

    CDQ分治入门 简介 CDQ分治是一种特别的分治方法,它由CDQ(陈丹琦)神犇于09国家集训队作业中首次提出,因此得名.CDQ分治属于分治的一种.它一般只能处理非强制在线的问题,除此之外这个算法作为某 ...

随机推荐

  1. 简单分析synchronized不会锁泄漏的原因

    最近看到一句话:内部锁synchronized不会造成锁泄漏(Lock Leak). 锁泄漏是指一个线程获得某个锁以后,由于程序的错误.缺陷致使该锁一直没法被释放而导致其他线程一直无法获得该锁的现象. ...

  2. Kirinriki 2017多校

    由于每个串的长度为5000,我们去枚举两个自串的对称点(这里注意一下,枚举的时候有两种情况的区间),然后用尺取法爬一遍. ac代码: #include<iostream> #include ...

  3. c#学习笔记-string stringBuilder

    string aTest = "abc";//分配固定的内存大小 aTest += "ddd"; //销毁原先的数据再来分配,消耗大 StringBuilder ...

  4. LeetCode 腾讯精选50题--只出现一次数字

    事先说明,如果不是评论区的大牛一语点破,我可能还会陷在死胡同里出不来,这道题其实很简单,利用了任何一个学过二进制的人都了解的定理,即: 1. 异或操作满足交换律 : a ^ b ^ c 等价于 a ^ ...

  5. wepy2.0中使用vant-weapp组件

    npm i vant-weapp -S --production 在项目下的package.json下看是否有了vant字段 最最最重要的,在引入的时候通过module映入 <config> ...

  6. ZPL语言完成条形码的打印

    近期因为项目的需求,需要使用到打印机来打印业务相关的条形码和其他信息,由于之前有操作其它打印机的经验,Leader就安排我来做这个了(凑哦,这能说我是懵逼的么).于是就开始了我的探索之旅啦,不对,是踩 ...

  7. Spring Data JPA 大纲归纳

    第一天: springdatajpa day1:orm思想和hibernate以及jpa的概述和jpa的基本操作 day2:springdatajpa的运行原理以及基本操作 day3:多表操作,复杂查 ...

  8. 【Java并发】锁机制

    一.重入锁 二.读写锁 三.悲观锁.乐观锁 3.1 悲观锁 3.2 乐观锁 3.3 CAS操作方式 3.4 CAS算法理解 3.5 CAS(乐观锁算法) 3.6 CAS缺点 四.原子类 4.1 概述 ...

  9. 【异常】 Ensure that config phoenix.schema.isNamespaceMappingEnabled is consistent on client and server.

    1 详细异常 ror: ERROR 726 (43M10): Inconsistent namespace mapping properties. Ensure that config phoenix ...

  10. 【异常】warning: refname 'feature1.3.0' is ambiguous.导致git merge失败

    1 现象 自己git merge --no-ff feature1.3.0 无法合并代码到develop,代码还是停留在feature1.3.0的分支 并提示一下错误 warning: refname ...