bobo has a permutation p 1,p 2,…,p n of 1,2,…,n.
Knowing m extra constraints of form p ai
思路:
最开始一直在想容斥原理,把每个限制条件不符合的情况减去,再把两个一起不符合的加上…然而根本不会实现,情况太多了。
后来看了题解,实际上可以将这一题的题意简化成求DAG的拓扑序列数量。好像瞬间简单了好多!然后因为每个联通的DAG中点数很少,可以压缩状态,每一位表示这一个点有没有在序列中。更新某个点的时候保证他的所有前驱都已经在序列中了就行了。具体我写注释里。
//请无视调试,当时调的心态很崩
#include<iostream>
#include<cstdio>
#include<cstring>
#include<bitset>
#include<vector>
#include<queue>
#define int long long
using namespace std;
const int MOD = 1e9+7;
const int N = 41;
const int M = 22;
const int E = (1<<21);
int n, m, fa[N], tot, ans, inv[N], indgr[N];
vector<int> to[N];
vector<int> vtx[N];
bool vis[N];
int num[N], maxst, f[E], pre[N];
void INITIALIZE()
{
inv[1] = 1;
for (int i = 2; i < N; i++)
inv[i] = (MOD-MOD/i)*inv[MOD%i]%MOD;
}
int C(int x, int y)
{
// cout << x << " " << y << endl;
int tmp = 1;
for (int i = x; i >= x-y+1; i--)
tmp = tmp*i%MOD;
for (int i = y; i >= 2; i--)
tmp = tmp*inv[i]%MOD;
// cout << tmp << "!" << endl;
return tmp;
}
int FIND(int u)
{
if (fa[u] == u) return u;
return fa[u] = FIND(fa[u]);
}
int WORK(int now)//WORK()返回一个连通块的拓扑序列方案数
{
int nn = vtx[now].size();
maxst = (1<<nn);
memset(pre, 0, sizeof(pre));
queue<int> q;
for (int i = 0; i < nn; i++)
if (indgr[vtx[now][i]] == 0)
q.push(vtx[now][i]); //cout << vtx[now][i] << "!" << endl;
while (!q.empty()){//先拓扑排序,把前驱预处理出来
int u = q.front();
q.pop();
for (int i = 0; i < to[u].size(); i++){
int v = to[u][i];
// cout << num[u] << " " << num[v] << "!" << endl;
indgr[v]--;
pre[num[v]] = (pre[num[v]]|(1<<num[u]));
if (indgr[v] == 0)
q.push(v);
}
}
// for (int i = 0; i < nn; i++)
// cout << pre[i] << " "; cout << "@" << endl;
memset(f, 0, sizeof(f));
f[0] = 1;
for (int i = 0; i < maxst; i++)//状压DP
for (int j = 0; j < nn; j++)
if (((~i)&(1<<j)) && (pre[j]&i) == pre[j]){//找是0的位,并保证他的前驱已经全部在序列中了
//更新的过程可以看成已经放好了1~x-1,把x放在编号j的位子上
int nxt = i|(1<<j);
// cout << i << " " << nxt << "#" << endl;
f[nxt] = (f[nxt]+f[i])%MOD;
}
// cout << f[maxst-1] << "^" << endl;
return f[maxst-1];
}
main()
{
INITIALIZE();
while (scanf("%lld%lld", &n, &m) == 2){
for (int i = 1; i <= n; i++)
fa[i] = i, to[i].clear(), vtx[i].clear(), num[i] = 0;
memset(indgr, 0, sizeof(indgr));
for (int i = 1; i <= m; i++){//用并查集判断联通块
int x, y;
scanf("%lld%lld", &x, &y);
to[x].push_back(y);
indgr[y]++;
fa[FIND(x)] = FIND(y);
}
tot = 0;
memset(vis, 0, sizeof(vis));
for (int i = 1; i <= n; i++)
if (!vis[FIND(i)]){
vis[FIND(i)] = 1;
tot++;
for (int j = i; j <= n; j++)
if (FIND(j) == FIND(i))
num[j] = vtx[tot].size(), vtx[tot].push_back(j);
//vtx[]记录一个联通块中的所有点编号,再用num给同一个联通块的点再编一个号,用于状压
}
// cout << "tot" << tot << endl;
// for (int i = 1; i <= n; i++) cout << num[i] << " "; cout << endl;
ans = 1;
for (int i = 1; i <= tot; i++)
ans = ans*WORK(i)%MOD*C(n, vtx[i].size())%MOD, n -= vtx[i].size();
printf("%lld\n", ans);
}
return 0;
}