CodeForces 380 D.Sereja and Cinema(组合数学)

Description

电影院有 n 位置,每个位置两边都有一个放水杯的槽,相邻两个位置分享同一个槽,看电影的人按顺序进场,先进场的人会把其座位两边没人用的槽都占了,如果一个人进场后发现其座位两边槽都被占了就会不满意,现在给出一部分位置的人进场的顺序,要求给没确定进场顺序的位置安排进场顺序使得所有看电影的人都很满意,问方案数

Input

第一行一整数n表示位置数,之后输入 n 个整数ai,若 ai=0 说明坐在第 i 个位置的 人的进场顺序还没确定,若ai0说明坐在第 i 个位置的人是第ai个进场的 (1n105)

Output

输出方案数,结构模 109+7

Sample Input

11
0 0 0 0 0 0 0 0 0 0 0

Sample Output

1024

Solution

第一个进场的人会占两个槽,后面的人要想每人至少一个槽就只能一人一个,也就是说后面进场的人必须坐在一个之前进场的人的旁边,把所有进场顺序确定的位置按(位置 pos ,进场顺序 val )存下来,按进场顺序排序升序排

分两种情况考虑,第一个进场的人位置没有确定,第一个进场的人位置已经确定。如果第一个进场的人位置没有确定,就枚举其位置,故只要求出第一个进场的人位置确定时的方案数即可

按进场顺序一个个将已经确定顺序的人放进去,假设当前已经坐满的位置区间为 [l,r] ,初始时 l,r 均为第一个进场的人的位置,然后按已经排好序的(位置,进场顺序)把这些进场顺序确定的人放好,假设当前人第 val 个进场,要坐在第 pos 个位置,上一个(确定顺序的)进场的人是第 pre 个进场的,那么这两个人之间有 valpre1 个人,如果 pos [l,r] 之间显然不行,一个位置只能坐一个人,只考虑 pos<l 的情况, pos>r 的情况类似,这 valpre1 个人进场时只有两个选择,从 l 开始往左扩展,从r开始往右扩展,且往左需要扩展到第 pos+1 个位置,这样第 val 个进场的人才能坐在第 pos 个位置,故如果 valpre1<lpos1 就无解,否则可以从这些人中选出 lpos1 个人从 l pos扩展,剩下的人就按进场顺序以此从 r 往右坐,方案数Clpos1valpre1,然后更新 l pos,更新 r r+(valpre1)(lpos1)

Code

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
#include<queue>
#include<map>
#include<set>
#include<ctime>
using namespace std;
typedef long long ll;
typedef pair<int,int>P;
const int INF=0x3f3f3f3f,maxn=100005;
#define mod 1000000007
int fact[maxn],inv[maxn];
void init(int n=1e5)
{
    fact[0]=1;
    for(int i=1;i<=n;i++)fact[i]=(ll)i*fact[i-1]%mod;
    inv[1]=1;
    for(int i=2;i<=n;i++)inv[i]=mod-(ll)(mod/i)*inv[mod%i]%mod;
    inv[0]=1;
    for(int i=1;i<=n;i++)inv[i]=(ll)inv[i-1]*inv[i]%mod;
}
int C(int n,int m)
{
    return (ll)fact[n]*inv[m]%mod*inv[n-m]%mod;
}
struct node
{
    int pos,val;
    bool operator<(const node&b)const
    {
        return val<b.val;
    } 
}a[maxn];
void add(int &x,int y)
{
    x=x+y>=mod?x+y-mod:x+y;
}
int n,m;
int Solve(int pos,int i)
{
    int l=pos,r=pos,ans=1,pre=1;
    for(;i<m;i++)
    {
        if(a[i].pos<l)
        {
            int dval=a[i].val-pre-1,dpos=l-a[i].pos-1;
            if(dpos>dval)return 0;
            ans=(ll)ans*C(dval,dpos)%mod;
            l=a[i].pos,r+=dval-dpos,pre=a[i].val;
        }
        else if(a[i].pos>r)
        {
            int dval=a[i].val-pre-1,dpos=a[i].pos-r-1;
            if(dpos>dval)return 0;
            ans=(ll)ans*C(dval,dpos)%mod;
            r=a[i].pos,l-=dval-dpos,pre=a[i].val;
        }
        else return 0;
    }
    return ans;
}
int main()
{
    init();
    while(~scanf("%d",&n))
    {
        m=0;
        for(int i=1;i<=n;i++)
        {
            int temp;
            scanf("%d",&temp);
            if(temp)a[m++]=(node){i,temp};
        }
        a[m++]=(node){n+1,n+1};
        sort(a,a+m);
        int ans=0;
        if(a[0].val==1)add(ans,Solve(a[0].pos,1));
        else
            for(int i=1;i<=n;i++)add(ans,Solve(i,0));
        printf("%d\n",ans);
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值