有一个n*n个棋盘,现在要把m个棋子从第1行移到第n行,每步只能往下或往右,第i个棋子要从第1行第a[i]列走到第n行第b[i]列,移动路径不能交叉,求路径的方案数。
n≤100000,m≤100
Wiki:Lindström–Gessel–Viennot引理
题目类型貌似很单一,通常都是上面有点集A,下面有点集B,然后求每个A到对应B的不相交路径总数
可能还会有一些限制QAQ
然后我们考虑我们求出f(i,j)表示A的第i个点到B的第j个点的路径方案
考虑如果设排列P,我们考虑i->P(i)
则这个排列有多少个逆序对就至少有多少个交点
这里出现了至少,我们可以考虑容斥原理
写出容斥原理的式子之后我们会得到一个O(n!)的算法
之后仔细
(不用)观察会发现容斥原理的式子实际上就是求行列式然后O(n^3)求行列式即可
比赛的时候想到了容斥,先不考虑限制,求出每个棋子到对应位置的方案数,其中有些方案的路径相交了,可以理解成他们交换了目的地,所以交换一对目的地系数发生改变。
一步减两步加,意识流一下交换2次系数就变回来了。
朴素的想法是O(n!)枚举每个可能的对应关系,其实就是上述的排列,然后乘上对应的系数,但是不知道怎么从排列反推交换次数。
学了矩阵行列式计算之后把每个排列的系数打了出来,发现其实真相很简单:
逆序对数是偶数,则系数为1,奇数则为-1
再联系之前的“交换”:
交换了两个数的位置之后,排列的逆序对数的奇偶性发生了改变
(然后就可以撇开交换了)
这个其实也很好单独证明
所以两个是一样的东西,即“容斥原理的式子实际上就是求行列式”
最后 O(n3log n) 解矩阵行列式即可
行列式也是容斥大家族的一份子咯,感谢金策工业综合大学出了这道题:)
#include <set>
#include <ctime>
#include <queue>
#include <cstdio>
#include <bitset>
#include <cctype>
#include <bitset>
#include <cstdlib>
#include <cassert>
#include <cstring>
#include <iostream>
#include <algorithm>
#define inf (1<<30)
#define INF (1ll<<62)
#define fi first
#define se second
#define rep(x,s,t) for(register int x=s,t_=t;x<t_;++x)
#define per(x,s,t) for(register int x=t-1,s_=s;x>=s_;--x)
#define travel(x) for(int I=last[x],to;I&&(to=e[I].to);I=e[I].nxt)
#define prt(x) cout<<#x<<":"<<x<<" "
#define prtn(x) cout<<#x<<":"<<x<<endl
#define pb(x) push_back(x)
#define hash asfmaljkg
#define rank asfjhgskjf
#define y1 asggnja
#define y2 slfvm
using namespace std;
typedef long long ll;
typedef pair<int,int> ii;
template<class T>void sc(T &x){
int f=1;char c;x=0;
while(c=getchar(),c<48)if(c=='-')f=-1;
do x=x*10+(c^48);
while(c=getchar(),c>47);
x*=f;
}
template<class T>void nt(T x){
if(!x)return;
nt(x/10);
putchar(x%10+'0');
}
template<class T>void pt(T x){
if(x<0)putchar('-'),x=-x;
if(!x)putchar('0');
else nt(x);
}
template<class T>void ptn(T x){
pt(x);putchar('\n');
}
template<class T>void pts(T x){
pt(x);putchar(' ');
}
template<class T>inline void Max(T &x,T y){if(x<y)x=y;}
template<class T>inline void Min(T &x,T y){if(x>y)x=y;}
const int maxm=105;
const int mod=1e9+7;
template<class T>inline void mop(T &x,T y){if((x+=y)>=mod)x-=mod;}
int n;
const int maxn=200006;
ll fac[maxn],caf[maxn];
ll qpow(ll a,ll b){
ll c=1;
for(;b;b>>=1,a=(a*a)%mod)
if(b&1)c=(a*c)%mod;
return c;
}
ll d[maxm][maxm];
int det(ll a[maxm][maxm],int n){
bool f=0;
rep(i,0,n){
rep(j,i+1,n){
int x=i,y=j;
while(a[y][i]){
int d=a[x][i]/a[y][i];
if(d)rep(k,i,n)a[x][k]=(a[x][k]-a[y][k]*d)%mod;
swap(x,y);
}
if(x!=i){
rep(k,i,n)swap(a[x][k],a[i][k]);
f^=1;
}
}
if(!a[i][i])return 0;
}
ll ans=f?-1:1;
rep(i,0,n)ans=ans*a[i][i]%mod;
if(ans<0)ans+=mod;
return ans;
}
int cnk(int n,int k){
return fac[n]*caf[n-k]%mod*caf[k]%mod;
}
int a[maxm],b[maxm];
void solve(){
int n,m;
sc(n);sc(m);
rep(i,0,m)sc(a[i]);
rep(i,0,m)sc(b[i]);
rep(i,0,m)rep(j,0,m){
if(b[i]-a[j]<0)d[i][j]=0;
else d[i][j]=cnk(n-1+b[i]-a[j],n-1);
}
ptn(det(d,m));
}
int main(){
// freopen("pro.in","r",stdin);
// freopen("chk.out","w",stdout);
fac[0]=1;
rep(i,1,maxn)fac[i]=fac[i-1]*i%mod;
rep(i,0,maxn)caf[i]=qpow(fac[i],mod-2);//begin from 0,qaq
int cas;sc(cas);
while(cas--)solve();
return 0;
}
蒟蒻:预处理阶乘的逆元的时候没有从0!开始,数组开错