小 D 是一个乐器爱好者,这一天她在给乐谱分段时遇到了难题。
乐谱是由若干音符组成的,为了方便起见用不同的数字来表示不同的音
符。小 D 想将一个长度为 n 的乐谱 A 分成若干连续的段,要求每一段不能有相
同的音符。
小 D 还想让乐谱的分段尽量平均(即长度最小的一段尽量长)并想知道保
证乐谱长度最小的一段尽量长的条件下有多少种分段的方法。
这么简单的问题小 D 当然会做了,她想考考你,你能不能比她先给出问题
的答案呢?
第一眼,最小值最大,果断二分答案
然后是DP
先来看二维的怎么写,对于每个点,我们要经过一个循环求出以它结尾它最长的区间的起点的下标
也就是不能有重复,然后求出最短区间要到哪里,把这中间的每一个点的DP值加上,得到的值就是匹配到弹前点的个数
然后我们来想如何优化,首先是最长区间,这个东西我们可以DP求出
转移很简单,dp[i]=max(dp[i-1],st[a[i]]+1)
为什么呢,我们来看,dp[i-1]保证了i前面的不能有重复,st[a[i]]+1保证了a[i]没有重复
而一个区间必须要同时满足这两个条件,所以取max
特别一提,要用离散化
然后是区间和,这个用前缀和来维护
当然,写树状数组也可以
这样就是O(nlogn)的
下面给出代码:
#include<iostream> #include<cmath> #include<cstdio> #include<cstdlib> #include<cstring> #include<string> #include<algorithm> using namespace std; inline long long rd(){ long long x=0,f=1; char ch=getchar(); for(;!isdigit(ch);ch=getchar()) if(ch=='-') f=-1; for(;isdigit(ch);ch=getchar()) x=x*10+ch-'0'; return x*f; } inline void write(long long x){ if(x<0) putchar('-'),x=-x; if(x>9) write(x/10); putchar(x%10+'0'); return ; } long long n; long long a[1000006],s[1000006]; long long st[1000006]; long long last[1000006]; long long sum[1000006],f[1000006]; long long num[1000006]; long long mod=998244353; long long check(long long x){ sum[0]=1; for(long long i=1;i<=n;i++){ long long l=st[i]-1,r=i-x; if(l>r) f[i]=0; else{ f[i]=0; long long v=0; if(l>=1) v=sum[l-1]; if(sum[r]-v) f[i]=1; } sum[i]=sum[i-1]+f[i]; } return f[n]; } int main(){ n=rd(); for(long long i=1;i<=n;i++){ a[i]=rd(); num[i]=a[i]; } sort(num+1,num+n+1); for(long long i=1;i<=n;i++){ s[i]=lower_bound(num+1,num+n+1,a[i])-num; } for(long long i=1;i<=n;i++){ st[i]=max(st[i-1],last[s[i]]+1); last[s[i]]=i; } long long l=1,r=n+1; while(l+1<r){ long long mid=(l+r)>>1; if(check(mid)) l=mid; else r=mid; } write(l),puts(""); memset(f,0,sizeof(f)); memset(sum,0,sizeof(sum)); sum[0]=1; long long ans=l; for(long long i=1;i<=n;i++){ long long x=st[i]-1,y=i-ans; if(x>y) f[i]=0; else{ f[i]=0; long long v=0; if(x>=1) v=sum[x-1]; if(sum[y]-v) f[i]=(sum[y]-v+mod)%mod; } sum[i]=(sum[i-1]+f[i]+mod)%mod; } write(f[n]%mod); return 0; }