题目链接:http://codeforces.com/contest/899/problem/E;
题目大意:给你一个长度为 n 的数组,每次操作只能消去数组中最长连续的数,问消去整个数组需要操作几次。
题目思路:一开始是想用线段树维护,但是由于太鶸了维护不动。。。后来想着用并查集维护每个连续数集合的左端点和右端点,再用优先队列来找最长的集合,经历了无数次的WA,TLE,MLE(虽然至今不知道为啥256M的内存能被我艹爆了。。。)之后终于A了。(看大神的代码还有直接用数组维护的,看了好几遍还是没看懂是如何操作的,果然弱爆了。。。orz)。
并查集的具体实现就是用一个l[i]来表示第 i 个数的集合的左端点,r[i]来表示第 i 个数的集合的右端点,
在消去的时候判断左端点左边和右端点右边的数是否相同,如果相同就更新左边集合的右端点和右边集合的左端点,
以及集合中数的个数,最后再借用优先队列进行维护操作即可。
具体代码如下:
#include <bits/stdc++.h>
using namespace std;
const int MX = 2e5+7;
int n,a[MX];
int num[MX],l[MX],r[MX];
int Find(int P[],int x){
return P[x] == x ? x : (P[x] = Find(P,P[x]));
}
void Union(int u,int v){
int uu = Find(l,u),vv = Find(l,v);
num[uu] += num[vv];
l[vv] = l[uu];
}
struct node{
int pos,len;
bool operator <(const node &node1)const{
if(len == node1.len) return pos > node1.pos;
return len < node1.len;
}
};
int main(){
scanf("%d",&n);
priority_queue<node>q;
for(int i = 1;i <= n;i++){
scanf("%d",&a[i]);
l[i] = r[i] = i;
num[i] = 1;
}
a[n+1] = -1;
for(int i = 1;i <= n;i++){
if(a[i] == a[i-1]) Union(i-1,i);
if(a[i] == a[i+1]) r[i] = i+1;
}
l[n+1] = r[n+1] = n+1;
for(int i = 1;i <= n;i++){
if(l[i] == i){
q.push(node{i,num[i]});
}
}
int ans = 0;
while(!q.empty()){
node nw = q.top();q.pop();
int x = nw.pos;
if(l[x] != x) continue;
ans++;
int R = Find(r,x);
l[x] = x - 1;
r[R] = R + 1;
int x1 = Find(l,x),x2 = Find(l,Find(r,x));
if(a[x1] != a[x2]) continue;
Union(x1,x2);
r[Find(r,x1)] = x2;
q.push(node{x1,num[x1]});
}
printf("%d\n",ans);
return 0;
}