Loading

7375. 【2021.11.11NOIP提高组联考】叁仟柒佰万

Description

给定一个序列 \(a\) ,你需要把它划分成任意多段,满足任意一段的 \(mex\) 值相同,求方案数,对 \(10^{9}+7\) 取模。

定义一个区间的 \(mex\) 为区间中最小的没有出现过的自然数。

\(1\le n\le 37000000\)

Solution

首先有个结论,就是最后划分的各个区间的 \(mex\) 跟整个区间的 \(mex\) 是一样的。

证明:

令全局 \(mex\)\(K\), 则说明 \(0 \sim K-1\) 的数都存在于数列中而 \(K\) 不存在。

假设我们最后每个区间的 \(mex\) 均为 \(X\)

\(X<K\), 由于序列中为 \(X\) 的数存在, \(X\) 必定在其中一个区间中, 与所有区间 \(mex=X\) 矛盾。

\(X>K\), 由于不存在 \(K\), 显然不合法。

于是只能是 \(X=K\)

知道了这个结论,\(\text{dp}\) 转移式子就好搞了。

\(f_i\) 表示到 \(i\) 结尾的方案数,转移方程:

\(f_i=\sum_{1\le j<i} f_j[get(j,i)=mex]\)(令 \(get(j,i)\) 表示区间 \(j\sim i\)\(mex\))。

注意到 \(get(j,i)\) 在某个 \(j\) 使得 \(get(j,i)=mex\) 后,保持不变。

因此我们只需要在 \(\mathcal{O}(n)\) 的时间里求出 \(j\),然后用前缀和更新答案。

至于求出 \(j\),可以使用桶来处理。


题外话:至于 \(n\) 的范围为什么是 37000000,原因如下:

题目背景:当年陈刀仔,他用 20 块贏到三千七百万,今天我也要从 \(n=10\) 出到 \(n=37000000\) !

Code

#include<cstdio>
#include<cstring>
#include<algorithm>
#define mod 1000000007
#define N 37000005
using namespace std;
int T,n,x,y,mex,ans,num,now,fs,a[N],sum[N],near[N];
bool flag,t[N];
int getmex()
{
	int res=0;
	while (t[res]) ++res;
	return res;
}
int main()
{
	freopen("clods.in","r",stdin);
	freopen("clods.out","w",stdout);
	scanf("%d",&T);
	while (T--)
	{
		scanf("%d",&n);
		mex=0;num=0;
		if (n!=37000000)
			for (int i=1;i<=n;++i)
				scanf("%d",&a[i]);
		else
		{
			scanf("%d%d",&x,&y);
			for (int i=2;i<=n;++i)
				a[i]=(a[i-1]*x+y+i)&262143;
		}
		for (int i=0;i<=n;++i)
			t[i]=0;
		for (int i=1;i<=n;++i)
			t[a[i]]=1;
		mex=getmex();
		if (mex==0)
		{
			ans=1;
			for (int i=1;i<n;++i)
				ans=ans*2%mod;
			printf("%d\n",ans);
			continue;
		}
		for (int i=0;i<mex;++i)
			t[i]=0,near[i]=-1;
		for (int i=1;i<=n;++i)
			if (a[i]<mex)
			{
				if (!t[a[i]]) t[a[i]]=1,++num;
				if (num==mex)
				{
					fs=i;
					break;
				}
			}
		for (int i=0;i<=n;++i)
			t[i]=0,sum[i]=0;
		flag=false;now=1;ans=0;
		for (int i=1;i<=n;++i)
		{
			if (a[i]<mex)
			{
				if (near[a[i]]!=-1) t[near[a[i]]]=0;
				near[a[i]]=i;
				t[i]=1;
			}
			if (i==fs) flag=true;
			while (flag&&!t[now]) ++now;
			if (flag) ans=sum[now-1]+1;
			if (ans==mod) ans=0;
			sum[i]=(sum[i-1]+ans)%mod;
		}
		printf("%d\n",ans);
	}
	return 0;
}
posted @ 2021-11-11 16:39  Thunder_S  阅读(116)  评论(0编辑  收藏  举报