题意:给出n个数,有一些特定要求,形如x,y表示x一定要在y的左边,求排列方案数。
一早上死想序列做法,想到了一下连边然后否决了。。然后想出了50分的容斥或者状压,然后觉得100分是不是再优化一下啥的,然后就再没脱出坑。。
事实证明部分分做法不一定是正解做法的暴力版本。。
首先有一个条件就是每一个人最多提出一个要求。
那么连边后的dag是一个森林,不相交的n棵树。
那么对于每一棵树,我们从下往上扫,记录当前走过的点数,设为sz,那么有:
g[x]∗=g[v]∗C(size[x],size[v])
表示以x为根的方案数,这个用乘法原理很好理解,每一颗子树是独立的。
然后答案也是一样的,相当于有一个超级根把所有树连在一起,所以就有:
ans∗=g[i]∗C(sz,size[i]),同理,也是独立的,直接乘就好。
看来思维不能太固定了= =。
#include<cstdio>
#include<algorithm>
#include<cstring>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
using namespace std;
const int N=2e5+5;
int head[N],next[N],go[N],size[N],d[N];
int tot,fac[N],inv[N],n,m,mo,f[N],g[N];
inline void add(int x,int y)
{
go[++tot]=y;
next[tot]=head[x];
head[x]=tot;
}
inline int find(int x)
{
if (f[x]==x) return x;
else return f[x]=find(f[x]);
}
inline int C(int n,int m)
{
return 1ll*fac[n]*inv[m]%mo*inv[n-m]%mo;
}
inline void dfs(int x)
{
g[x]=1;size[x]=0;
for (int i=head[x];i;i=next[i])
{
int v=go[i];
dfs(v);
size[x]+=size[v];
g[x]=1ll*g[x]*g[v]%mo*C(size[x],size[v])%mo;
}
size[x]++;
}
int main()
{
freopen("photo.in","r",stdin);freopen("photo.out","w",stdout);
int cas;
scanf("%d",&cas);
while(cas--)
{
scanf("%d%d%d",&n,&m,&mo);
fac[0]=inv[0]=fac[1]=inv[1]=1;
fo(i,2,n)
fac[i]=1ll*fac[i-1]*i%mo,inv[i]=1ll*(mo-mo/i)*inv[mo%i]%mo;
fo(i,2,n)
inv[i]=1ll*inv[i]*inv[i-1]%mo;
fo(i,1,n)f[i]=i,head[i]=d[i]=0;
int flag=0;tot=0;
while (m--)
{
int x,y;
scanf("%d%d",&x,&y);
if (find(x)!=find(y))f[find(x)]=find(y);
else flag=1;
add(y,x);
d[x]++;
}
if (flag)
{
printf("0\n");
continue;
}
int ans=1,sz=0;
fo(i,1,n)
if (!d[i])
{
dfs(i);
sz+=size[i];
ans=1ll*ans*g[i]%mo*C(sz,size[i])%mo;
}
printf("%d\n",ans);
}
}