HDU 5852 Intersection is not allowed!

有一个n*n个棋盘,现在要把m个棋子从第1行移到第n行,每步只能往下或往右,第i个棋子要从第1行第a[i]列走到第n行第b[i]列,移动路径不能交叉,求路径的方案数。
n≤100000,m≤100

Wiki:Lindström–Gessel–Viennot引理

Vertical:

题目类型貌似很单一,通常都是上面有点集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!开始,数组开错

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值