题意:
n个精英,被编号1~n。排完队之后,每个精英要求,自己的后面(不必是严格后面)都必须有一个人的编号和自己的编号相差为1(+1或-1),而有些人必须站在某些位置,求方案数。
题解:
挺好的题。
%%%200815147当场AC。
联考时没有有想出来,只会做k=0的情况,可以发现序列的后缀一定是连续的一段数组成的,所以枚举最后的数填什么,那么往前填相当于向当前已填的数的两边扩展。所以答案就是
∑Ci−1n−1
,二项式定理得
2n−1
。
现在考虑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);
}