网上都是并查集的做法,我做的时候想不到,后面看了大佬们的并查集解法,直呼NB。
本孱弱就献上一发线段树区间合并+启发式合并的垃圾解法。
首先,连续的碟子只用一次移动,体现在给出的数组( 碟子属于哪一个圆柱的编号)中为连续的元素相同:
比如: 1 2 3 3 2 4, 需要4次移动,第6个碟子不需要移动,前面第三和第四3 3可以一起移动。
所以用线段树处理出每个区间需要移动的次数,维护左右端点值,如果合并时左区间右端点和右区间左端点相同,说明区间合并时减去一次移动次数。修改时直接单点修改所在圆柱的编号。
合并时采用启发式合并。
总复杂度
O
(
N
l
o
g
2
N
)
O(Nlog^{2}N)
O(Nlog2N)
勉勉强强能过
#include "bits/stdc++.h"
#define lson t<<1,l,mid
#define rson t<<1|1,mid+1,r
using namespace std;
const int N = 2e5+5;
int n,m;
int va[N];
int num[N<<2],lp[N<<2],rp[N<<2];
void pushup(int t){
int l = t<<1 , r = t<<1|1;
num[t] = num[l] + num[r];
if(rp[l]==lp[r]) num[t]--;
rp[t] = rp[r];
lp[t] = lp[l];
}
void build(int t,int l,int r){
if(l==r) {
num[t] = 1;
lp[t] = rp[t] = va[l];
return;
}
int mid = (l+r)>>1;
build(lson); build(rson);
pushup(t);
}
void update(int t,int l,int r,int pos,int d){
if(l==r){
num[t] = 1;
lp[t] = rp[t] = d;
return;
}
int mid = (l+r)>>1;
if(pos<=mid) update(lson,pos,d);
else update(rson,pos,d);
pushup(t);
}
vector<vector<int> > v(N);
int id[N];
int main()
{
ios::sync_with_stdio(false);
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>va[i];
id[i] = i;
v[va[i]].push_back(i);
}
build(1,1,n);
cout<<num[1]-1<<endl;
for(int i=1;i<m;i++){
int a,b; cin>>a>>b;
int x = id[a] , y = id[b];
if(v[x].size()<v[y].size()){
id[a] = y;
swap(x,y);
}
for(int i=0;i<v[y].size();i++){
v[x].push_back(v[y][i]);
update(1,1,n,v[y][i],x);
}
cout<<num[1]-1<<endl;
}
}