ARC125D 题解

发布时间 2023-03-25 23:13:39作者: zzafanti

ARC125D

题意

给定长度为 \(n\) 的序列中,求其中只出现过一次的非空子序列的个数,对 \(998244353\) 取模。

题解

不难发现,一个只出现过一次的子序列合法的充分必要条件是:

  1. 头部元素 \(a_i\) 是原序列中下标最小的(即最左边的)值为 \(a_i\) 的元素
  2. 由对称性,该子序列最后一个元素 \(a_i\) 是原序列中最后一个出现的值为 \(a_i\) 的元素
  3. 子序列中任意相邻两元素在原序列中,不妨设它们的下标分别是 \(l,r\)\(\forall a_i,l<i<r\)\(a_i\neq a_l\)\(a_i\neq a_r\)

这些条件可以举一些例子验证。

为了简化程序,我们可以为原序列设首尾两个哨兵,

具体地,令 \(a_0=max\{a_i\}+1, a_{n+1}=max\{a_i\}+2,1\leq i\leq n\)

这样 \(a_0\)\(a_{n+1}\) 就和原序列任意元素不相同了,

接着,我们求以 \(a_0\) 为头,\(a_{n+1}\) 为尾的合法子序列个数,

我们就不必考虑条件 1 和条件 2 了。

然后就可以 dp 了,

\(f_i\) 为以 \(a_i\) 结尾的合法子序列个数,\(lst_i\) 为值为 \(i\) 的元素目前为止最后出现的位置,则

\(f_0=1\)

\(f_i=\sum\limits_{j=lst_{a_i}}^{i-1} f_j \times [j==lst_{a_j}] (i\neq 0)\)

实际我们需要不断维护 \(lst_{a_i}\) 每次顺带把 \(f_{lst_{a_i}}\) (更新前的) 置为 0 ,这样转移时的求和就是区间求和了。

再加上单点修改,可以直接树状数组维护。

参考代码

#include<bits/stdc++.h>
using namespace std;
const int N=200010;
typedef long long ll;
const int mod=998244353;
int a[N],n,c[N],f[N],lst[N],m;
inline int lowbit(int x){return x&-x;}
void add(int x,int val){
	x++; //由于要用到0这个下标,树状数组中把所有下标偏移1 
	for(;x<=n+1;x+=lowbit(x)) c[x]=((c[x]+val)%mod+mod)%mod;
}
int ask(int x){
	x++; //同上 
	int ret=0;
	for(;x;x-=lowbit(x)) ret=((ret+c[x])%mod+mod)%mod;
	return ret;
}

int main(){
	
	cin>>n;
	for(int i=1; i<=n; i++){
		scanf("%d",&a[i]);
		m=max(m,a[i]);
	}
	a[0]=m+1,a[n+1]=m+2; //设哨兵 
	
	f[0]=1; add(0,1);
	for(int i=1; i<=n+1; i++){
		f[i]=(ask(i-1)-ask(lst[a[i]]-1)+mod)%mod;
		if(lst[a[i]]) add(lst[a[i]],-f[lst[a[i]]]);
		add(i,f[i]);
		lst[a[i]]=i;
	}
	
	cout<<(f[n+1]-1+mod)%mod; //子序列不能空,所以减1 
	
	return 0;
}