bzoj3782上学路线(Lucas+CRT+容斥DP+组合计数)

传送门:https://www.lydsy.com/JudgeOnline/problem.php?id=3782

有部分分的传送门:https://www.luogu.org/problemnew/show/P4478

看到标题开始还以为是AHOI的小雪和小可可……

题解:乍一看会40pts:测试点1、2:n,m<=1000的直接O(nm)DP;测试点3、4:没有障碍物直接C(n+m,n),然后p=1e6+3是质数可以直接取模。

想了几分钟会60pts:测试点5、6:模数可以拆成几个不超过1e5的质数的乘积,直接算出C(n+m,n)对每个质数的模数,然后CRT合并一下就行了。

不会CRT的左转,我原来也是看这个博客学的:https://blog.csdn.net/niiick/article/details/80229217

其实满分也很可做,容斥一下就行了:把障碍物按x从小到大,x相同按y从小到大排序,然后f[i]表示不经过前(i-1)个障碍物但经过第i个障碍物的方案,然后增加最后一个点为(n,m),然后可以计算f[i]=C(x[i]+y[i],y[i])+Σf[j]C(x[i]-x[j]+y[i]-y[j],x[i]-x[j]),其中j满足x[j]<=x[i]&&y[j]<=y[i],这个计算由于T<=200算组合数+CRT合并也不会超时,复杂度O(T^2log)。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e6+7;
struct node{ll x,y;}a[N];
ll n,m,P,f[N],p[8],fac[6][N],inv[6][N],mul[8],imul[8],g[8];
int num,tp;
bool cmp(node a,node b){return a.x==b.x?a.y<b.y:a.x<b.x;}
ll qpow(ll a,ll b,ll p)
{
    ll ret=1;
    while(b)
    {
        if(b&1)ret=ret*a%p;
        a=a*a%p,b>>=1;
    }
    return ret;
}
ll c(ll a,ll b,int i)
{
    if(a<b)return 0;
    if(a<p[i]&&b<p[i])return fac[i][a]*inv[i][b]%p[i]*inv[i][a-b]%p[i];
    return c(a%p[i],b%p[i],i)*c(a/p[i],b/p[i],i)%p[i];
}
ll C(ll a,ll b)
{
    if(!tp)return c(a,b,0);
    ll ret=0;
    for(int i=1;i<=4;i++)g[i]=c(a,b,i);
    for(int i=1;i<=4;i++)ret=(ret+g[i]*mul[i]%P*imul[i]%P)%P;
    return ret;
}
int main()
{
    scanf("%lld%lld%d%lld",&n,&m,&num,&P);
    for(int i=1;i<=num;i++)scanf("%lld%lld",&a[i].x,&a[i].y);
    a[++num]=(node){n,m};
    sort(a+1,a+num+1,cmp);
    if(P==1e6+3)p[0]=1e6+3;else p[1]=3,p[2]=5,p[3]=6793,p[4]=10007,tp=1;
    if(tp)
    {
        for(int i=1;i<=4;i++)
        {
            mul[i]=P/p[i],imul[i]=qpow(mul[i],p[i]-2,p[i]);
            fac[i][0]=1;for(int j=1;j<p[i];j++)fac[i][j]=fac[i][j-1]*j%p[i];
            inv[i][p[i]-1]=qpow(fac[i][p[i]-1],p[i]-2,p[i]);
            for(int j=p[i]-1;j;j--)inv[i][j-1]=inv[i][j]*j%p[i];
        }
    }
    else{
        fac[0][0]=1;for(int i=1;i<P;i++)fac[0][i]=fac[0][i-1]*i%P;
        inv[0][P-1]=qpow(fac[0][P-1],P-2,P);
        for(int i=P-1;i;i--)inv[0][i-1]=inv[0][i]*i%P;
    }
    for(int i=1;i<=num;i++)
    {
        f[i]=C(a[i].x+a[i].y,a[i].x);
        for(int j=1;j<i;j++)if(a[j].x<=a[i].x&&a[j].y<=a[i].y)
        f[i]=(f[i]-f[j]*C(a[i].x-a[j].x+a[i].y-a[j].y,a[i].x-a[j].x)%P+P)%P;
    }
    printf("%lld",f[num]);
}
View Code

 

转载于:https://www.cnblogs.com/hfctf0210/p/10428351.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值