codeforces 380D. Sereja and Cinema(#223div1 组合数学)

题目链接:【D. Sereja and Cinema】

电影院有n个位置,每个位置两边都有一个放东西的地方,当一个人进来之后他会立即占有位置两边空着的储物格,要是他到的时候,座位的两边的储物格都已经被占有了,那这个人就会离开

输入n(1<=n<=10^5)个数,第i个数是c(1<=c<=n),表示第i个座位上的人是第c个进入影院的,如果c=0,表示这个位置上的人进入的顺序未知,求能够使得最终一个人都不离开影院的方案数

样例:

6
0 1 0 0 0 6

有一种情况绝对会有一个人会离开,a,b,c位置相邻,对应的进入顺序是ai,bi,ci,如果bi比ai,ci都大,那么bi肯定占不到储物格

按照这个思想,我们可以用pair<>来记录进入顺序和位置,first记录的是进入顺序,second记录的是位置,排序,维护一个区间[l, r],

如果第一个进入的人位置是确定的,那么第二个人进入的人的位置一定在第一个人的旁边,也就是说后一个进入的位置一定在前一个进入的位置区间的两侧

看样例,第一个进入的是位置2,此时位置区间是[2,2],第六个位置时第六个进入的,第二个位置与第六个位置之间有3个位置,第1个进入与第六个进入的人之间还有4个人要进入,所以第一个与第六个之间的进入顺序可以有C(4,3)个,之后位置区间就扩充到[1,6]

如果第一个人进入的位置不确定,那就枚举第一个进入的位置

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <string>
using namespace std;
#define ll __int64
const int inf = 1e5+10;
const int mod = 1e9+7;
pair<int, int>a[inf];
int n, x, tot;
ll ans, ai[inf], bi[inf];
ll mul(ll x, int num)
{
	ll e = 1;
	while(num)
	{
		if(num&1) e = (x*e)%mod;
		x = (x*x)%mod;
		num>>=1;
	}
	return e; 
}
ll C(int ni, int mi)
{
	return ai[ni]*bi[mi]%mod*bi[ni-mi]%mod;
}
void find(int x, int y)
{
	int l = y, r = y;
	ll sum = 1;
	for(int i=x; i<=tot; i++)
	{
		int num=a[i].first-a[i-1].first-1;
		int dis=a[i].second;
		if(l<=dis && dis<=r) return;
		if(dis < l)
		{
			if(l-dis-1 > num) return;
			int tmp1=l-dis-1, tmp2=num-tmp1;
			l = dis;
			r = r+tmp2;
			sum = sum*C(num, tmp1)%mod;
		}
		else
		{
			if(dis-r-1 >num) return;
			int tmp1=dis-r-1, tmp2=num-tmp1;
			l = l-tmp2;
			r = dis;
			sum = sum*C(num, tmp1)%mod;
		}
	}
	ans = (ans+sum)%mod;
}
int main()
{
	tot=0;
	scanf("%d", &n);
	for(int i=1; i<=n; i++)
	{
		scanf("%d", &x);
		if(x)
		{
			a[++tot].first = x;
			a[tot].second = i;	
		}
	}
	a[++tot].first = n+1;
	a[tot].second = n+1;
	ai[0]=1, bi[0]=1;
	for(int i=1; i<=n; i++) ai[i]=ai[i-1]*i%mod;
	for(int i=1; i<=n; i++) bi[i]=mul(ai[i], mod-2);
	sort(a+1, a+tot+1);
	ans = 0;
	a[0].first = 1;
	if(a[1].first == 1) find(2, a[1].second);
	else
	{
		for(int i=1; i<=n; i++)
		{
			find(1, i);
		}
	}
	printf("%I64d\n", ans);
	return 0;
}



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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值