Description
给定一个序列 (a) ,你需要把它划分成任意多段,满足任意一段的 (mex) 值相同,求方案数,对 (10^{9}+7) 取模。
定义一个区间的 (mex) 为区间中最小的没有出现过的自然数。
(1le nle 37000000)。
Solution
首先有个结论,就是最后划分的各个区间的 (mex) 跟整个区间的 (mex) 是一样的。
证明:
令全局 (mex) 为 (K), 则说明 (0 sim K-1) 的数都存在于数列中而 (K) 不存在。
假设我们最后每个区间的 (mex) 均为 (X)。
若 (X<K), 由于序列中为 (X) 的数存在, (X) 必定在其中一个区间中, 与所有区间 (mex=X) 矛盾。
若 (X>K), 由于不存在 (K), 显然不合法。
于是只能是 (X=K) 。
知道了这个结论,( ext{dp}) 转移式子就好搞了。
设 (f_i) 表示到 (i) 结尾的方案数,转移方程:
(f_i=sum_{1le j<i} f_j[get(j,i)=mex])(令 (get(j,i)) 表示区间 (jsim 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
",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
",ans);
}
return 0;
}