题意:
有 \(n\) 个人排成一排,每个点 \(i\) 最多会给出一条限制,形如 \((i,j)\) 表示点 \(i\) 必须站在 \(j\) 的左侧。问有多少种成立的方案数,答案对输入的模数 \(p\) 取模。
对于\(100\%\) 的数据,\(n≤2\times 10^5,m≤2\times 10^5,m≤n,n+10≤p≤10^9+7,T≤10\).
题解:
给个不一样的做法,应该好写一点。
我们考虑限制构成什么,先用限制构造边 \(i\to j\),发现如果所有 \(i\) 都有出边那么这玩意会构成外向基环树森林,但是我们会发现如果有环,那一定无解,所以在有解的情况下会构成一棵树。
现在再思考限制的本质,对于 \(i\to j\), 这是一个经典的问题,等价于满足构成的树上 \(i\) 的拓扑序小于 \(j\)。
对于这个经典的有根树版本问题我们有一个经典的结论:
\[ans=\frac{n!}{\prod^{n}_{i=1}siz_i}
\]
其中 \(siz_i\) 为 \(i\) 的子树大小。考虑怎么把我们的限制构造成有根树,发现我们把 \(i\to j\) 都换成 \(j\to i\) 答案肯定是不变的,把题意理解成 \(i\) 在 \(j\) 的右侧即可。这样一定是有根树。
证明:
考虑一般情况,若发现如果所有 \(i\) 都有出边那么这玩意会构成内向基环树森林。考虑删掉环的一些部分使得有答案,那么你一定可以找到一个根在环上一点。
那么直接做就可以了。
细节:预处理逆元可以做到线性,这里还有一个性质值得注意,数据范围里写了 \(n+10\le p\),这使得我们可以直接处理逆元,因为如果 \(n\ge p\) 的话我们就要用 \(lucas\) 定理了。
code:
#include<bits/stdc++.h>
#define pb push_back
#define int long long
#define m(a) memset(a,0,sizeof(a))
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
using namespace std;
const int N=2e5+10;
int n,m,k,T,siz[N],vis[N],cnt,tg,mod,ni[N],ans;
vector<int> g[N];
int ksm(int x,int k){int ans=1;while(k){if(k&1) ans=ans*x%mod;x=x*x%mod;k>>=1;}return ans;
}
void dfs(int u){vis[u]=cnt;siz[u]=1;for(int v:g[u]){if(vis[v]==cnt){tg=1;break; }else if(!vis[v]) dfs(v);siz[u]+=siz[v];}
}
void init(){rep(i,1,n) g[i].clear();tg=0;cnt=0;m(siz);m(vis);ni[1]=1;ans=1;rep(i,2,n) ni[i]=(-mod/i*ni[mod%i]%mod+mod)%mod;
}
signed main(){ios::sync_with_stdio(0);cin.tie(0),cout.tie(0);cin>>T;while(T--){cin>>n>>m>>mod;init();rep(i,1,m){int x,y;cin>>x>>y;g[y].pb(x);}rep(i,1,n){if(!vis[i]) cnt++,dfs(i);}if(tg==1){cout<<0<<'\n';continue;}rep(i,1,n){ans=ans*i%mod;ans=ans*ni[siz[i]]%mod;}cout<<ans<<'\n';}return 0;
}