HDU 4917 Permutation
HDU 4917 Permutation
题目链接http://acm.hdu.edu.cn/showproblem.php?pid=4917
Description
bobo has a permutation p1,p2,…,pn of 1,2,…,n.
Knowing m extra constraints of form pai<pbi, bobo wanna count the number of different permutations modulo (109+7).
It is guaranteed that there is at least one such permutation.
Input
The input consists of several tests. For each tests:
The first line contains n,m (1≤n≤40,0≤m≤20). Each of the following m lines contain 2 integers ai,bi(1≤ai,bi≤n).
Output
For each tests:
A single number denotes the number of permutations.
Sample Input
3 1
1 2
3 2
1 2
2 3
Sample Output
3
1
题意:
给你n个数。从1到n。再给你m个关系每个关系表示前一个数字小于后一个数字。求总共有多少种排列方式。
题解:
见代码注释。分块枚举,然后状态压缩。
代码:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<vector>
using namespace std;
const int INF = 123456789;
const long long MOD = 1e9+7;
long long combine[50][50];
long long dp[1<<21];
long long ans;
int num,res;
bool vis[50];
vector <int > matrix[50];
vector <int > pre[50];
vector <int > tp[50];
int connect[50],connect_add[50];
void Combine()
{
for (int i = 0; i <= 45; i++) {
for (int j = 0; j <= i; j++) {
if (j == 0 || i == j)
combine[i][j] = 1;
else combine[i][j] = (combine[i-1][j] + combine[i-1][j-1])%MOD;
}
}
}
void dfs(int u) //num 是联通块所有元素的个数和,connect联通块中的数字种类,connect_add 存放这些联通块在 num 中的地址;
{
vis[u] = 1;
connect[num] = u;
connect_add[u] = num++;
int size = matrix[u].size();
for (int i = 0; i < size; i++) {
int v = matrix[u][i];
if (!vis[v])
dfs(v);
}
}
void solve()
{
for (int i = 0; i < num; i++) { //计算前驱;
tp[i].clear();
int u = connect[i]; //枚举这个分块每个出现元素;
int size = pre[u].size(); //计算这个元素的前驱数量;
for (int j = 0; j < size; j++)
tp[i].push_back(connect_add[pre[u][j]]); //将每个元素的前驱的位置压入记录;
}
int nn = (1 << num); //num个元素全部出现的状压的状态;
for(int i = 0; i < nn; i++) dp[i] = 0; //dp初始化;
dp[0] = 1;
for (int i = 0 ; i < nn; i++) {
if (dp[i] == 0)
continue;
for (int j = 0; j < num; j++) { //枚举每个元素;
if ((i & (1 << j)) == (1 << j)) //如果这个元素出现过了继续(剪枝);
continue;
bool flag = 0;
int size = tp[j].size(); //枚举这里前驱的地址元素;
for (int k = 0; k < size; k++) {
int v = tp[j][k]; //前驱地址;
if ((i & (1 << v)) != (1 << v)) { //如果前驱没有出现证明不是拓扑排序;
flag = 1;
break;
}
}
if (flag == 0)
dp[i|(1<<j)] = (dp[i|(1<<j)] + dp[i])%MOD; //将前驱加入;
}
}
ans = (((combine[res][num]*dp[nn-1])%MOD)*ans)%MOD; //计算分块数目,即在res空白中放置num个数字的排列种类*num个数字的排列种数;
res -= num; //空白数目更新;
}
int main()
{
Combine();
int n,m;
while (~scanf("%d %d",&n,&m)) {
for (int i = 1; i <= n; i++) {
pre[i].clear();
matrix[i].clear();
}
memset(vis,0,sizeof(vis));
ans = 1;
res = n;
int u,v;
for (int i = 1; i <= m; i++) {
scanf("%d %d",&u,&v);
matrix[u].push_back(v);
matrix[v].push_back(u);
pre[v].push_back(u);
}
for (int i = 1; i <= n; i++) {
if (!vis[i]) {
num = 0;
dfs(i);
solve();
}
}
printf("%I64d\n",ans);
}
return 0;
}