bzoj 4315: queue

题意:

n个精英,被编号1~n。排完队之后,每个精英要求,自己的后面(不必是严格后面)都必须有一个人的编号和自己的编号相差为1(+1或-1),而有些人必须站在某些位置,求方案数。

题解:

挺好的题。
%%%200815147当场AC。
联考时没有有想出来,只会做k=0的情况,可以发现序列的后缀一定是连续的一段数组成的,所以枚举最后的数填什么,那么往前填相当于向当前已填的数的两边扩展。所以答案就是 Ci1n1 ,二项式定理得 2n1
现在考虑k不为0的情况:其实可以得出,每一个数一定是以它为首的后缀的最大值/最小值,并且假如最后一个钦定的数是最大值/最小值的情况确定,前面的都确定了。所以可以记下当前的最大值和最小值,分别计算出相邻的钦定的数之间有多少种填法(也是上面的公式),最后乘起来就好。
注意判无解。
code:

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#define LL long long
using namespace std;
const LL mod=1000000007;
LL a[100010],ans=0,n,k;
bool c[100010];
LL Pow[200010],ans1=1,ans2=1,fac[200010],fin[200010],inv[200010];
void pre()
{
    fac[0]=fac[1]=fin[0]=fin[1]=inv[0]=inv[1]=1;
    for(LL i=2;i<=2*n+1;i++)
    {
        fac[i]=fac[i-1]*i%mod;
        inv[i]=(mod-mod/i)*inv[mod%i]%mod;
        fin[i]=fin[i-1]*inv[i]%mod;
    }
}
LL C(LL x,LL y){return fac[x]*fin[y]%mod*fin[x-y]%mod;}
LL fow(LL a,LL b)
{
    if(b<0) return 1LL;
    LL ans=1;
    while(b)
    {
        if(b&1) (ans*=a)%=mod;
        (a*=a)%=mod;b>>=1;
    }
    return ans;
}
LL solve(LL last,LL l,LL r)
{
    if(last<=1) return 1;
    LL p=last-1;
    while(p!=0&&a[p]==0) p--;
    if(p==0) return C(last-1,l-1);
    if(a[p]>=l&&a[p]<=r) return 0;
    if(a[p]<=l) return C(last-p-1,l-a[p]-1)*solve(p,a[p],a[p]+(n-p))%mod;
    if(a[p]>=r) return C(last-p-1,a[p]-r-1)*solve(p,a[p]-(n-p),a[p])%mod;
}
int main()
{
    scanf("%lld %lld",&n,&k);
    pre();
    memset(c,false,sizeof(c));
    for(LL i=1;i<=k;i++)
    {
        LL x,y;scanf("%lld %lld",&x,&y);
        if(c[x]||a[y]!=0) {printf("0");return 0;}
        c[x]=true;a[y]=x;
    }
    LL last=n;while(last!=0&&a[last]==0) last--;
    if(last==n) {printf("%lld",solve(n,a[n],a[n]));return 0;}
    LL base=fow(2,n-last-1);
    if(a[last]+n-last<=n) ans+=(base*solve(last,a[last],a[last]+(n-last)))%mod,ans%=mod;
    if(a[last]-n+last>=1) ans+=(base*solve(last,a[last]-(n-last),a[last]))%mod,ans%=mod;
    printf("%lld",ans);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值