【CodeForces】899 E. Segments Removal
【题意】给定n个数字,每次操作删除最长的连续相同数字(等长删最左),求全部删完的最少次数。n<=2*10^6,1<=ai<=10^9。
【算法】并查集+堆
【题解】将序列的相同数字段压缩,全部插入堆。那么每次操作删除堆顶,并尝试合并堆顶的前驱和后继,能合并就重新插入堆中。
在支持删除的序列中找前驱和后继,是经典的并查集实现。
具体而言,fa[i]表示 i 点左边(含自身)最近的未被删除的点,即把删除了的点全部并入左侧第一个未被删除的点,那么删除点x后fa[x]=find(fa[x]-1)。
前驱是find(fa[x]-1),后继只需要记录r[x]表示x点代表区间的最右端点,每次合并是:fa[y]=x,r[x]=r[y]。
如果能合并就重新插入堆中,容易发现堆中废弃的元素一定比新插入的更晚访问,所以废弃元素访问到就会是被删除的现象,一个元素被删除表现为find(x) ≠ x。
注意:删除的时候记得更新r[]。
#include<cstdio> #include<cstring> #include<cctype> #include<cmath> #include<queue> #include<stack> #include<set> #include<vector> #include<algorithm> #define ll long long #define lowbit(x) x&-x using namespace std; int read(){ char c;int s=0,t=1; while(!isdigit(c=getchar()))if(c=='-')t=-1; do{s=s*10+c-'0';}while(isdigit(c=getchar())); return s*t; } int min(int a,int b){return a<b?a:b;} int max(int a,int b){return a<b?b:a;} int ab(int x){return x>0?x:-x;} //int MO(int x){return x>=MOD?x-MOD:x;} //void insert(int u,int v){tot++;e[tot].v=v;e[tot].from=first[u];first[u]=tot;} /*------------------------------------------------------------*/ const int inf=0x3f3f3f3f,maxn=200010; int n,fa[maxn],a[maxn],tot,r[maxn]; struct cyc{ int id,ans,number,l; bool operator < (const cyc &a)const{ return ans<a.ans||(ans==a.ans&&l>a.l); } }b[maxn]; priority_queue<cyc>q; int find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);} int main(){ n=read(); int x=0; for(int i=1;i<=n;i++)a[i]=read(); for(int i=1;i<=n;i++){ x++; if(a[i]!=a[i+1]){ b[++tot]=(cyc){tot,x,a[i],i}; x=0; } } n=tot; for(int i=1;i<=n;i++)fa[i]=i,r[i]=i; for(int i=1;i<=n;i++)q.push(b[i]); for(int i=1;i<=n;i++){ cyc x=q.top();q.pop(); while(find(x.id)!=x.id&&!q.empty())x=q.top(),q.pop(); if(q.empty()){if(find(x.id)!=x.id)printf("%d",i-1);else printf("%d",i);return 0;} //printf("%d %d %d %d\n",x.id,x.ans,x.number,x.l); int f=find(x.id); fa[f]=find(f-1);r[fa[f]]=r[f]; int pre=find(f-1),suc=find(r[f]+1); if(pre==0||suc==0||b[pre].number!=b[suc].number)continue; fa[suc]=pre; b[pre].ans+=b[suc].ans; r[pre]=r[suc]; q.push(b[pre]); } return 0; }