这两个题的模型是有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. IntelliJ IDEA 统一设置编码为utf-8编码

    问题一: File->Settings->Editor->File Encodings 问题二: File->Other Settings->Default Settin ...

  2. 原生 JS 绑定事件 移除事件

    监听事件的绑定与移除主要是addEventListener和removeEventListener的运用. addEventListener语法 element.addEventListener(ty ...

  3. Zircon

    Zircon 来源 https://github.com/zhangpf/fuchsia-docs-zh-CN/tree/master/docs/the-book 国内镜像源 https://hexa ...

  4. JSON在JS中的应用

    一. JSON在JS中的应用: 首先解释下JSON对象与普通js对象字面量定义时格式的区别: Js对象字面量定义格式: var person = { name:"Wede", ag ...

  5. SPOJ-MobileService--线性DP

    题目链接 https://www.luogu.org/problemnew/show/SP703 方法一 分析 很显然可以用一个四维的状态\(f[n][a][b][c]​\)表示完成第i个任务时且三人 ...

  6. flutter主题颜色

    主题色 右下角的FloatingActionButton的颜色就是默认取值MaterialColor, 默认是蓝色的,如果修改成primarySwatch,就会变成这个颜色值. 一.primarySw ...

  7. STM32点亮LED

    原理图 测试灯,接GPIO外设B,Pin 12 举例 前提,工程模版建立好 #include "stm32f10x.h" void delay(u32 i) { while(i-- ...

  8. liunx pip安装

    方法一 wget https://bootstrap.pypa.io/get-pip.py python get-pip.py 方法二 wget https://pypi.python.org/pac ...

  9. WPF - 仿QQ2014

    声明:非原创.项目是网上发现的,以学习为目的重写了部分代码,合理地调整了下布局,巧妙地简化了下Style样式.重写还算是有价值的,并非完全复制. 效果: 获取项目源码:https://pan.baid ...

  10. Dart的List比较特殊的几个API

    List里面常用的属性和方法: 常用属性: length 长度 reversed 翻转 isEmpty 是否为空 isNotEmpty 是否不为空 常用方法: add 增加 addAll 拼接数组 i ...