CF EDU165-E-序列問題,線段樹

yoshinow2001發表於2024-05-01

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;
}