HDU 5852 Intersection is not allowed!(组合数学+行列式)

190 篇文章 1 订阅
33 篇文章 0 订阅

Description
一个n*n棋盘,第一行第a1,a2,…,ak列有k个棋子,每个棋子都只能往下和往右走,每个棋子的终点是第n行第b1,b2,…,bk列,问这k个棋子到达各种的终点且路径不交叉的方法数
Input
第一行一整数T表示用例组数,每组用例首先输入两整数n和k表示棋盘规模和棋子数量,之后k个整数ai表示k个棋子的起点,最后k个整数bi表示k个棋子的终点(1<=n<=10^5,1<=k<=100,1<=a1 < a2 < … < ak <=n,1<=b1 < b2 < … < bk <=n)
Output
对于每组用例,输出合法方案数,结果模1e9+7
Sample Input
1
5 2
1 2
3 4
Sample Output
50
Solution
首先考虑两个棋子的情况,即一个棋子从a1到b1,另一个棋子从a2到b2,两条路径不交叉的方案数,首先不考虑交叉方案数显然是C(b1-a1+n-1,n-1)*C(b2-a2+n-1,n-1),对于一个a1->b1,a2->b2且路径交叉的方案,如果我们把最下面一个交叉点之后的两条路径交换那么就对应了一个a1->b2,a2->b1的方案;对于一个a1->b2,a2->b1的方案,显然这两条路径必然会相交,那么我们把最后一个交叉点之后的两条路径交换就又对应一个a1->b1,a2->b2的路径交叉的方案,故我们建立了a1->b1,a2->b2交叉路径与a1->b2,a2->b1的方案的一一对应,那么不合法方案数就是C(b2-a1+n-1,n-1)*C(b1-a2+n-1,n-1)
对于多个棋子的情况,由容斥原理,假设某些棋子路径发生了交叉,那么我们采用两个棋子的分析方法,把这些交叉的路径从最后下面一个交叉点之后交换,那么就变成了一个1~n序列的重排,我们不妨设其为c序列,表示第i个棋子从(1,ai)出发到(n,ci),那么这个排列对答案的贡献就取决于c序列的逆序对数,逆序对数为奇则做负贡献,为偶则做正贡献,那么就有
这里写图片描述
故问题转化为求一个n阶方阵行列式,用高斯消元O(n^3)即可解决
Code

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const ll mod=1000000007ll;
#define maxn 111 
ll a[maxn][maxn];
void debug(int n)
{
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=n;j++)printf("%I64d ",a[i][j]);
        printf("\n");
    }
}
ll mod_pow(ll a,ll b,ll p)
{
    ll ans=1;
    while(b)
    {
        if(b&1)ans=ans*a%p;
        a=a*a%p;
        b>>=1;
    }
    return ans;
}
ll determinant(int n)
{
    ll ans=1;
    for(int k=1;k<=n;k++)
    {
        ll pos=-1;
        for(int i=k;i<=n;i++)
            if(a[i][k])
            {
                pos=i;
                break;
            }
        if(pos==-1)return 0;
        if(pos!=k)
            for(int j=k;j<=n;j++)swap(a[pos][j],a[k][j]);
        //debug();
        ll inv=mod_pow(a[k][k],mod-2,mod);
        for(int i=k+1;i<=n;i++)
            if(a[i][k])
            {
                ans=ans*inv%mod;
                for(int j=k+1;j<=n;j++)
                    a[i][j]=((a[i][j]*a[k][k]%mod-a[k][j]*a[i][k]%mod)%mod+mod)%mod;
                a[i][k]=0;
            }
        //debug();
    }
    for(int i=1;i<=n;i++)
        ans=ans*a[i][i]%mod;
    return ans;
}
ll f[222222],inv[222222];
void init()
{
    f[0]=1,inv[0]=1;
    for(int i=1;i<=200000;i++)
        f[i]=f[i-1]*i%mod,inv[i]=mod_pow(f[i],mod-2,mod);
}
ll C(int n,int m)
{
    if(m>n)return 0;
    return f[n]*inv[m]%mod*inv[n-m]%mod;
}
int T,n,k,b[maxn],c[maxn];
int main()
{
    init();
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d%d",&n,&k);
        for(int i=1;i<=k;i++)scanf("%d",&b[i]);
        for(int i=1;i<=k;i++)scanf("%d",&c[i]);
        for(int i=1;i<=k;i++)
            for(int j=1;j<=k;j++)
                a[i][j]=C(n-1+c[i]-b[j],n-1);
        ll ans=determinant(k);
        printf("%I64d\n",ans);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值