题意:
将一个序列分段,每段至多有 k k k个数恰好出现一次,问方案数
题解:
n
2
n^2
n2dp很容易,当
(
l
,
r
)
(l,r)
(l,r)满足条件时
f
[
l
−
1
]
−
>
f
[
r
]
f[l-1]->f[r]
f[l−1]−>f[r]
考虑怎么快速求出所有满足条件的
f
f
f的和
记录一下每种颜色最后出现位置和次后出现位置,可以将题意转化为区间加,为所有值
<
=
k
<=k
<=k的位置的和,且加减操作是镜像的
这个东西不好维护,于是考虑分块
O
(
n
n
l
o
g
n
)
O(n \sqrt n logn)
O(nnlogn)的做法挺好想的,比如分块套堆。实际上,因为每次都是加减1,所以每个数操作后的权值对整个块的前缀和影响不大,可以
O
(
1
)
O(1)
O(1)维护,因此不需要这个
l
o
g
log
log
最后还有一个问题,就是这个
f
f
f是动态增加的,要等一个块的
f
f
f算完再建这个块,否则直接当零散处理
code:
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
using namespace std;
const int mod=998244353;
int n,k,a[100010],pre[100010],L[350],R[350],b[100010];
int c[2][100010],sum[350][100010],f[100010],u[350];
bool vis[350];
void change(int l,int r,int c)
{
int i=max(1,l);
while(i<=r)
{
int N=pre[i];
if(L[N]==i&&R[N]<=r) u[N]+=c,i=R[N];
else
{
if(vis[N])
{
if(c==1) (sum[N][b[i]]-=f[i-1])%=mod;
else (sum[N][b[i]-1]+=f[i-1])%=mod;
}
b[i]+=c;
}
i++;
}
}
int get(int r)
{
int i=1,ans=0;
while(i<=r)
{
int N=pre[i];
if(L[N]==i&&R[N]<=r)
{
if(k>=u[N]) (ans+=sum[N][k-u[N]])%=mod;
i=R[N];
}
else if(b[i]+u[N]<=k) (ans+=f[i-1])%=mod;
i++;
}
return ans;
}
void make(int N)
{
vis[N]=true;
for(int i=L[N];i<=R[N];i++) (sum[N][b[i]]+=f[i-1])%=mod;
for(int i=1;i<=n;i++) (sum[N][i]+=sum[N][i-1])%=mod;
}
int main()
{
scanf("%d %d",&n,&k);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
int len=sqrt(n);
for(int i=1;i<=n;i++)
{
pre[i]=i/len+1;
if(i==1||pre[i]!=pre[i-1]) L[pre[i]]=i,R[pre[i-1]]=i-1;
}
R[pre[n]]=n;
memset(vis,false,sizeof(vis));
f[0]=1;
for(int i=1;i<=n;i++)
{
if(i==R[pre[i]]) make(pre[i]);
change(c[0][a[i]]+1,c[1][a[i]],-1);
c[0][a[i]]=c[1][a[i]];c[1][a[i]]=i;
change(c[0][a[i]]+1,c[1][a[i]],1);
f[i]=get(i);
}
printf("%d",(f[n]+mod)%mod);
}