对于一个长 的正整数序列 ,定义一个长 的,值域 的正整数序列 关于 是好的当且仅当:
- 从前往后遍历 ,遍历到 时:
- 需要满足 ;
- 将 增加 ;
现给定 和序列 ,你需要构造一个长 的字典序最小的正整数序列 ,使得 关于 是好的,或报告无解。
。
考虑操作的过程,不难发现相当于不断把最小值的位置抬上去,所以操作时序列 的最小值一定是单调不降的。
考虑按照操作时 的最小值将 分段,不难发现一个分段方式合法(存在对应的 )当且仅当:
- 每一段中的数互不相同;
- 除了最后一段外,第 段的数构成的集合包含第 段的数构成的集合;
第二个限制也相当于要求每一段 中的数的集合包含 中的数的集合。
显然若一段以 开头,则下一段的开头应该在一个区间内,设其为 。显然 都有 ,那么可以双指针求出 和 。
接下来可以从后往前扫,求出 表示若某一段以 开头的话, 能否完成合法的分段。
那么若 就无解。
然后从前往后贪心,显然每一段肯定都是越长越好,所以每次贪心找到 的最大的 ,分出段 即可,注意这也相当于 ,故做前缀最大值即可。
最后扫一遍即可求出答案,注意不在 中出现的位置也要填数。
最后一段要特殊处理,时间复杂度 。
代码如下:
#include <iostream>
#include <cstdio>
#include <map>
#include <vector>
using namespace std;
const int S=300005;
int n,m,a[S];
int ct[S];
int lb[S],rb[S];
bool flg[S];
int sm[S],mx[S];
bool vis[S];
int ans[S];
int main()
{
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
cin>>n>>m;
for(int i=1;i<=m;i++) cin>>a[i];
ct[a[1]]=1;
lb[1]=2;
for(int i=2,j=1;i<=m;i++)
{
ct[a[i-1]]--;
while(j<=m&&ct[a[i-1]]==0) ct[a[++j]]++;
if(j==m+1) lb[i]=-1;
else lb[i]=j+1;
}
for(int i=1;i<=n;i++) ct[i]=0;
ct[a[1]]=1;
for(int i=1,j=1;i<=m;i++)
{
ct[a[i-1]]--;
while(j<m&&ct[a[j+1]]==0) ct[a[++j]]++;
rb[i]=j+1;
}
for(int i=1;i<=n;i++) ct[i]=0;
int L=m;
flg[m+1]=true;
for(int i=m;i>=1;i--)
{
if(++ct[a[i]]==2) break;
flg[i]=true;
L=i;
}
sm[m+1]=1;
for(int i=m;i>=1;i--)
{
if(lb[i]!=-1&&lb[i]<=rb[i])
flg[i]|=(sm[lb[i]]-sm[rb[i]+1]>0);
sm[i]=flg[i]+sm[i+1];
}
if(!flg[1]) return cout<<"-1\n",0;
for(int i=1;i<=m;i++)
{
mx[i]=mx[i-1];
if(flg[i]) mx[i]=i;
}
int p=1,t=0;
while(p<L)
{
int np=mx[rb[p]];
t++;
for(int i=p;i<np;i++)
if(!vis[a[i]]) ans[a[i]]=t,vis[a[i]]=true;
p=np;
}
if(p<=m)
{
t++;
for(int i=p;i<=m;i++)
if(!vis[a[i]]) ans[a[i]]=t,vis[a[i]]=true;
}
for(int i=1;i<=n;i++) if(!vis[i]) ans[i]=t;
for(int i=1;i<=n;i++) cout<<ans[i]<<' ';cout<<'\n';
return 0;
}
/*
求出 i 为某一段开头时下一段开头所在的区间 [l_i,r_i]
那么 l[i] <= l[i+1]
r[i] <= r[i+1]
然后我肯定想每一段长度构成的序列的字典序最大
那么处理出每一个点能不能到终点即可?
*/