link:https://codeforces.com/contest/1969/problem/E
給一序列 \(a\),要使得 \(a\) 的任意子段 \([a_l,\dots,a_r]\) 都存在某數 \(a_i\),使得其只在該子段恰出現一次。問最少修改 \(a\) 中幾處位置?
\(1\leq n\leq 3\times 10^5\).
一個不太好的想法:對每個值去考慮,這樣的入手點只考慮了區域性的某個值,而無法聯絡上整個序列這一整體結構。
這個問題應該抓的重點是其整體結構:假設 \([a_1,\dots,a_i]\) 的所有子段都滿足條件,考慮加入一個 \(a_{i+1}\) 產生的的影響,只需考慮所有以 \(i+1\) 為右端點的區間是否僅含一個數。
那麼很自然地考察這些字尾區間,所有以 \(i\) 為右端點的區間,如果因為在結尾拼上了一個 \(a_{i+1}\) 而“變壞了”,那罪魁禍首隻可能是 \(a_{i+1}\) 它和之前某個數(不妨稱其為 \(a_j\) )重複了,並且\(a_j\)是作為 \([j,i+1]\) 中唯一一個僅出現一次的數。
好,那現在就是怎麼快速判斷它的問題,一個自然的想法是在這時候對每個值考慮:一個值出現1次到出現2次是質變,而2次以上其實我們並不關心,因此如果動態地對每個值打個記號,在最後一次出現的位置打上 +1
,倒數第二次出現的位置打 -1
,更前的位置全部打 0
,記錄這樣一個序列 \([s_1,\dots,s_n]\),那麼就變成判斷,是否存在某個 \([s_l,\dots,s_{i+1}]\) 的和為0。
考慮設 \(f_i=\sum_{j=i}^n s_j\) 這樣一個字尾和,那麼判斷的是 \(\min(f_1,\dots,f_{i+1})>0\),而對於插入 \(a_{i+1}\) 而言,意味著修改 \(s_{i+1}\),也就是對所有 \(f_1,\dots,f_{i+1}\) 做一個區間修改,所以就是區間min-區間賦值(具體地應該是區間加)的問題,線段樹可以解決:
(寫的時候把 \(\min\) 打成了 \(\max\) 還調了半天T_T)
#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define endl '\n'
#define fastio ios_base::sync_with_stdio(false);cin.tie(0);cout.tie(0)
using namespace std;
const int N=3e5+5;
const int INF=0x3f3f3f3f;
int n,a[N],s[N];
int lst[N],pst[N];
struct SegT{
#define ls (node<<1)
#define rs (node<<1|1)
int n;
int tr[N<<2],tag[N<<2];
void push_up(int node){
tr[node]=min(tr[ls],tr[rs]);
}
void push_down(int node){
if(tag[node]==0)return;
tag[ls]+=tag[node];tr[ls]+=tag[node];
tag[rs]+=tag[node];tr[rs]+=tag[node];
tag[node]=0;
}
void build(int node,int l,int r){
tr[node]=tag[node]=0;
if(l==r)return;
int mid=(l+r)>>1;
build(ls,l,mid);build(rs,mid+1,r);
}
void init(int __n){
n=__n;
build(1,1,n);
}
void add(int node,int l,int r,int ql,int qr,int v){
if(ql<=l&&r<=qr){
tag[node]+=v;
tr[node]+=v;
return;
}
push_down(node);
int mid=(l+r)>>1;
if(mid>=ql)add(ls,l,mid,ql,qr,v);
if(mid+1<=qr)add(rs,mid+1,r,ql,qr,v);
push_up(node);
}
int query(int node,int l,int r,int ql,int qr){
if(ql<=l&&r<=qr)return tr[node];
push_down(node);
int ret=INF;
int mid=(l+r)>>1;
if(mid>=ql)ret=query(ls,l,mid,ql,qr);
if(mid+1<=qr)ret=min(ret,query(rs,mid+1,r,ql,qr));
return ret;
}
int query(int l,int r){return query(1,1,n,l,r);}
void modify(int x,int v){
add(1,1,n,1,x,-s[x]);
s[x]=v;
add(1,1,n,1,x,s[x]);
}
}tr;
int main(){
fastio;
int tc;cin>>tc;
while(tc--){
cin>>n;
rep(i,1,n)cin>>a[i];
rep(i,1,n)lst[i]=pst[i]=-1,s[i]=0;
tr.init(n);
int ans=0;
rep(i,1,n){
if(~pst[a[i]])tr.modify(pst[a[i]],0);
if(~lst[a[i]])tr.modify(lst[a[i]],-1);
tr.modify(i,1);
if(tr.query(1,i)>0){
pst[a[i]]=lst[a[i]];
lst[a[i]]=i;
continue;
}
ans++;
if(~lst[a[i]])tr.modify(lst[a[i]],1);
if(~pst[a[i]])tr.modify(pst[a[i]],-1);
}
cout<<ans<<endl;
}
return 0;
}