【题解】codeforces 1209 - problem G2. Into Blocks (hard version)

链接

做法

感谢 @newbiegcz 的指导
做法和题解是一样的,然而英文太烂没有看懂。看了cz_xuyixuan的才看懂。后面维护方法和他略有不同

要找分界点:分段后,每段包含其中的所有颜色的所有出现位置
首先:一个颜色只考虑其两端 [ L i , R i ] [L_i,R_i] [Li,Ri],分界点不能在这个区间中间。每个颜色会删去在 [ L i , R i ) [L_i,R_i) [Li,Ri)的分界点,剩下的就是合法分界点(被删除次数为 0 0 0的位置后)
分出每一段后,求出每段颜色出现次数最大值, 答 案 = n − 每 段 最 大 值 求 和 答案 = n - 每段最大值求和 =n
带修改的情况用线段树维护这个覆盖次数(被删除的次数),为 0 0 0则是一个合法的分界点。而注意到在顶层n后面一定是一个合法的分界点。故只需要记录最小值,默认每一层的最小值位置后就是合法分界点。合并的时候讨论一下即可这样就只用一个线段树搞定了!(这种做法学习自@newbiegcz的代码)
对于修改,经典的做法是看成对两种颜色的删除和插入。修改一种颜色时,也是先在线段树中删除这种颜色,再修改,再插入。这样非常好写!

#include
using namespace std;
#define PB push_back
#define lowbit(x) (x&(-x))
#define MP make_pair
#define fi first
#define se second
#define ls(x) (x << 1)
#define rs(x) ((x << 1) | 1)
#define rep(i,l,r) for (int i = l ; i <= r ; i++)
#define down(i,r,l) for (int i = r ; i >= l ; i--)
#define fore(i,x) for (int i = head[x] ; i ; i = e[i].next)
#define SZ(v) (int)v.size()

typedef long long ll;
typedef pair <int,int> pr;
const int maxn = 2e5 + 10;

struct node{
     
	int mn,z,mx,lmx,rmx,ans;
	node (){
      mn = z = mx = lmx = rmx = ans = 0; }
	void print(){
     
		cout<<"mn : "<<mn<<" z : "<<z<<" mx : "<<mx<<" lmx: "<<lmx<<" rmx : "<<rmx<< "ans : "<<ans<<endl;
	}
}sgt[maxn << 2];

node operator + (const node &a,const node &b){
     
	node res;
	res.mn = min(a.mn,b.mn);
	res.mx = max(a.mx,b.mx);
	if ( a.mn < b.mn ){
      //then we think actually breakpoint is in left subsegment
		res.ans = a.ans;
		res.lmx = a.lmx , res.rmx = max(a.rmx,b.mx);
	}
	else if ( a.mn > b.mn ){
     
		res.ans = b.ans;
		res.lmx = max(a.mx,b.lmx) , res.rmx = b.rmx;
	}
	else{
      //merge the breakpoints in left and right
		res.ans = a.ans + b.ans + max(a.rmx,b.lmx);
		res.lmx = a.lmx , res.rmx = b.rmx;
	}
	//note that when pushup, the lazy tag z must equal to 0, so need to care about it.
	return res;
}
void operator += (node &a,int v){
     
	a.mn += v;
	a.z += v;
}
int n,q;
int a[maxn + 1];
set <int> st[maxn + 1];

void pushdown(int o){
     
	if ( !sgt[o].z ) return;
	sgt[ls(o)] += sgt[o].z;
	sgt[rs(o)] += sgt[o].z;
	sgt[o].z = 0;
}
void pushup(int o){
     
	sgt[o] = sgt[ls(o)] + sgt[rs(o)];
}
void modify(int o,int l,int r,int L,int R,int v){
     
	if ( L > R ) return;
	if ( L <= l && R >= r ) return void(sgt[o] += v);
	int mid = (l + r) >> 1;
	pushdown(o);
	if ( L <= mid ) modify(ls(o),l,mid,L,R,v);
	if ( R > mid ) modify(rs(o),mid + 1,r,L,R,v);
	pushup(o);
}
void modify(int o,int l,int r,int id,int v){
     
	if ( l == r ) return void(sgt[o].mx = sgt[o].lmx = v); //In the last layer, sgt[o].ans = sgt[o].rmx = 0 by default
	int mid = (l + r) >> 1;
	pushdown(o);
	if ( id <= mid ) modify(ls(o),l,mid,id,v);
	else modify(rs(o),mid + 1,r,id,v);
	pushup(o);
}
void print(int o,int l,int r){
     
	cout<<"seg : "<<o<<" "<<l<<" "<<r<<" sgt : "; sgt[o].print();
	if ( l == r ) return;
	int mid = (l + r) >> 1;
	pushdown(o);
	print(ls(o),l,mid);
	print(rs(o),mid + 1,r);
}
void change(const auto &st,int d){
     
	if ( SZ(st) == 0 ) return;
	modify(1,1,n,*st.begin(),*st.rbegin() - 1,d);
	modify(1,1,n,*st.begin(),d == 1 ? SZ(st) : 0);
	//print(1,1,n);
}
int getans(){
     
	return n - sgt[1].ans - sgt[1].lmx - sgt[1].rmx;
}
int main(){
     
	freopen("input.txt","r",stdin);
	ios_base::sync_with_stdio(0), cin.tie(0), cout.tie(0);
	cin>>n>>q;
	rep(i,1,n){
     
		cin>>a[i];
		st[a[i]].insert(i);
	}
	rep(i,1,maxn) change(st[i],1);
	cout<<getans()<<"\n";
	rep(i,1,q){
     
		int id,x;
		cin>>id>>x;
		change(st[a[id]],-1);
		st[a[id]].erase(id);
		change(st[a[id]],1);
		a[id] = x;
		change(st[a[id]],-1);
		st[a[id]].insert(id);
		change(st[a[id]],1);
		cout<<getans()<<"\n";
	}
}

总结

主要的问题是没有想到分界点可以以覆盖次数来判断,开始想的是:所有出现在它之前的颜色的最后一次出现位置都 < = <= <=它, [ x s t . i f L i d < = x R i d < = x ] [x st. if Lid <= x Rid <= x] [xst.ifLid<=xRid<=x] , 但是想到去取max了,没有想到更直白的用每种颜色的区间去删除不合法的分界点

你可能感兴趣的:(数据结构)