Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 131072/131072 K (Java/Others) |
题意:一个1-n的排列,给出其中一些数之间的约束条件.问排列的总个数
相当于给出一张图,求拓扑排序的总数是多少.
首先观察数据范围,一共的点不超过40个,一共的边不超过20个,意味着图不一定联通,而且一个连通块中的点最多21个.
我们首先把图分成一个个连通块,依次对每一个联通块处理,一个联通块带来的新的排序树是C(sum,tot)*连通块内拓补排序的总数,sum是剩下的未处理的点的总数.
对于一个连通块内部,用状压dp的方法求出拓扑排序总数.具体的方案是,对于每一个状态i,枚举放到整个拓扑序列的最后一个点,如果点j的前驱都属于i,那么dp[i]+=dp[i&*(~(1<<j))]
#include <map>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
using namespace std;
const int MAXN=50;
const long long mod=1e9+7;
int d[MAXN][MAXN];
int vis[MAXN];
int pre[MAXN];
int a[MAXN];
long long C[MAXN][MAXN];
int tot=0;
int n,m;
void dfs(int s){
int tmp=tot;
a[tot++]=s;
vis[s]=1;
for(int i=1;i<=n;i++){
if(d[s][i]){
if(!vis[i])
dfs(i);
if(d[s][i]==1){
for(int j=0;j<tot;j++){
if(a[j]==i){
pre[j]|=(1<<tmp);
}
}
}
}
}
}
long long dp[1<<21];
long long cal(){
memset(dp,0,sizeof(dp));
dp[0]=1;
for(int s=0;s<(1<<tot);s++){
for(int i=0;i<tot;i++){
if(((s&pre[i])==pre[i])&&!(s&(1<<i)))
dp[s|(1<<i)]=(dp[s|(1<<i)]+dp[s])%mod;
}
}
return dp[(1<<tot)-1];
}
int main(){
for(int i=0;i<41;i++){
C[i][0]=1;
for(int j=1;j<=i;j++)
C[i][j]=(C[i-1][j-1]+C[i-1][j])%mod;
}
while(scanf("%d%d",&n,&m)!=EOF){
memset(d,0,sizeof(d));
memset(vis,0,sizeof(vis));
for(int i=0;i<m;i++){
int u,v;
scanf("%d%d",&u,&v);
d[u][v]=1;
d[v][u]=-1;
}
long long x=n;
long long ans=1;
for(int i=1;i<=n;i++){
if(!vis[i]){
tot=0;
memset(pre,0,sizeof(pre));
dfs(i);
if(tot<2)
ans=(((tot*C[x][tot])%mod)*ans)%mod;
else
ans=((cal()*C[x][tot])%mod*ans)%mod;
x-=tot;
}
}
printf("%lld\n",ans);
}
}