ABC397F
tag:线段树
题意:将一个长度为 n n n的序列分成三段,使得三段内出现数字个数的和最大。
题解:
令 f l , r f_{l,r} fl,r为区间 [ l , r ] [l,r] [l,r]内数字出现次数,那么就是求最大的 f 1 , j − 1 + f j , i + f i + 1 , n f_{1,j-1}+f_{j,i}+f_{i+1,n} f1,j−1+fj,i+fi+1,n
令 p r e i pre_i prei表示前 i i i个数中出现数字个数, s u f i suf_i sufi表示 i i i之后不同数字出现个数,这两个数组可以通过循环预处理得到。
for(int i=1;i<=n;i++){
if(!vis[a[i]]){
cnt++;
vis[a[i]]=i;
}
pre[i]=cnt;
}
cnt=0;
memset(vis,0,sizeof vis);
for(int i=n;i;i--){
if(!vis[a[i]]){
cnt++;
vis[a[i]]=i;
}
suf[i]=cnt;
}
上述式子当中,只剩中间 f j , i f_{j,i} fj,i这一项无法处理。考虑P1972HH的项链的状态设计方式,考虑固定住右端点时,维护每一个对应左端点的答案(这道题中为区间的出现数字个数)。
当右端点从 r − 1 r-1 r−1变为 r r r的时候,只发生了唯一一个改变,即对于所有左端点小于 r r r的区间,都多出现了一次数字 a r a_r ar,而我们维护的信息为区间内出现的不同数字个数,那么只有原来没出现数字 a r a_r ar的区间多出现了一个数字。令 v i s i vis_i visi表示数字 i i i上一次出现的下标,那么需要对答案加一的左端点区间则为 [ v i s a r + 1 , i ] [vis_{a_r}+1,i] [visar+1,i]。基于这种状态设计,我们能通过线段树来维护固定住右端点的情况下对应左端点的答案序列。
但是此时仍有 n 2 n^2 n2个不同区间,仍不能通过本题。我们发现只需要知道答案的最值,考虑能否快速求出最大值。
当我们固定住右端点 r r r的时候,式子 p r e l − 1 + f l , r + s u f r + 1 pre_{l-1}+f_{l,r}+suf_{r+1} prel−1+fl,r+sufr+1中 s u f r + 1 suf_{r+1} sufr+1是固定值,不影响最值,所以我们只需要求前两项和的最值。其中 f l , r f_{l,r} fl,r作为我们维护的信息,而 p r e l − 1 pre_{l-1} prel−1作为只跟 l l l相关的常数,我们可以在线段树建立的时候作为初始值即可,通过线段树我们能够在 O ( l o g n ) O(logn) O(logn)时间之内知道 p r e l − 1 + f l , r pre_{l-1}+f_{l,r} prel−1+fl,r的最大值,总共不同的右端点只有 n n n个所以总复杂度为 O ( n l o g n ) O(nlogn) O(nlogn)。
#include <bits/stdc++.h>
#define int long long
#define endl '\n'
#define lowbit(x) (x&(-x))
#define ull unsigned long long
#define pii pair<int,int>
using namespace std;
const string yes="Yes\n",no="No\n";
const int N = 1000005,inf = 2e18,mod=1000000007;
int n;
int a[300005];
int pre[300005],suf[300005];
int vis[300005];
#define mid ((l+r)/2)
#define ls rt<<1
#define rs rt<<1|1
#define lson ls,l,mid
#define rson rs,mid+1,r
struct seg_tree{
vector<int>lazy,sz,tr;
seg_tree(){}
seg_tree(int n):tr(n<<2),lazy(n<<2),sz(n<<2){}
void push_up(int rt){
tr[rt]=max(tr[ls],tr[rs]);
}
void build(int rt,int l,int r){
sz[rt]=r-l+1;
if(l==r){tr[rt]=a[l];return;}
build(lson);build(rson);
push_up(rt);
}
void push_down(int rt){
if(lazy[rt]){
lazy[ls]+=lazy[rt];lazy[rs]+=lazy[rt];
tr[ls]+=lazy[rt];tr[rs]+=lazy[rt];
lazy[rt]=0;
}
}
void update(int rt,int l,int r,int ql,int qr,int k){
if(ql<=l&&r<=qr){tr[rt]+=k;lazy[rt]+=k;return;}
push_down(rt);
if(mid>=ql) update(lson,ql,qr,k);
if(mid<qr) update(rson,ql,qr,k);
push_up(rt);
}
int query(int rt,int l,int r,int ql,int qr){
if(ql<=l&&r<=qr)return tr[rt];
push_down(rt);
if(mid>=qr)return query(lson,ql,qr);
if(mid<ql) return query(rson,ql,qr);
return max(query(lson,ql,qr),query(rson,ql,qr));
}
}seg;
#undef mid
void solve(){
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
}
int cnt=0;
for(int i=1;i<=n;i++){
if(!vis[a[i]]){
cnt++;
vis[a[i]]=i;
}
pre[i]=cnt;
}
cnt=0;
memset(vis,0,sizeof vis);
for(int i=n;i;i--){
if(!vis[a[i]]){
cnt++;
vis[a[i]]=i;
}
suf[i]=cnt;
}
memset(vis,0,sizeof vis);
seg=seg_tree(n);
int ans=0;
for(int i=1;i<=n;i++){
seg.update(1,1,n,i,i,pre[i-1]);//这个可以放到线段树初始化的时候
seg.update(1,1,n,vis[a[i]]+1,i,1);
vis[a[i]]=i;
ans=max(ans,seg.query(1,1,n,1,i)+suf[i+1]);
//每次右端点为i,询问所有左端点在1到i的答案
}
cout<<ans;
}
signed main(){
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
cout<<fixed<<setprecision(12);
solve();
}