【神のN (I)OI P模拟赛】羽未「umi」——线段树

暂未找到原题链接

题目描述

给定一个长度为 n n n 的序列,每个位置上有颜色 a i a_i ai

我们称序列是 good 的,当且仅当 ∀ a l = a r , i ∈ [ l , r ] : a i = a l = a r \forall a_l = a_r,i\in [l, r]:a_i = a_l = a_r al=ar,i[l,r]:ai=al=ar,也即同一颜色在序列上的位置连续。

你可以依次执行一些操作使得序列变得 good,每次操作你可以将某颜色 c c c 在序列上的所有位置改成另一颜色 c ′ c' c

我们定义代价为,最终序列与原序列不同的位置数量(不是操作次数)。

你很轻易地就算出了最小代价。然而你发现,每当你算出答案时,序列又发生了修改,第 i t i_t it 个位置上的颜色被改为了 x t x_t xt

因此,你需要回答初始序列以及每次修改后的最小代价。需要注意的是,你所执行的操作不会保留到下一次询问。

前言

还有两天就NOIP了,虽然不打算打信心赛,但是这场直接自闭了啊

第一题就不会做,我甚至DAG转序列问题都没想到

第二题不绑点,直接先骗分+乱搞,结果y0CE

就这第三题好不容易想到了 O ( n log ⁡ 2 n ) O(n\log^2n) O(nlog2n) 的做法,没打完,只得交暴力+一大片注释

最后点点时间,第四题暴力虽然好打,但是只给10分呐

心态没了,策略出大问题,现在午睡都睡不好了

题解

首先是非常必要且明显的转换:

设每种颜色 c c c 的最左/最右出现位置为 l c , r c l_c, r_c lc,rc,则 [ l c , r c ] [l_c, r_c] [lc,rc] 必须修改为同一颜色。
按照这个标准,最后整个序列会被划分成若干段,每一段必须为同一颜色。此时最优解为 n n n 减去每一段的最多出现颜色的出现次数。

通过这个转换,我们就有了暴力思路:把每个颜色代表的区间合并,此时同一种颜色只会全部出现在一个区间中,把每种颜色的出现次数放在第一个位置,然后求区间最大值即可。

考虑怎么动态维护,正解又做了一个转换:记 b i b_i bi 为跨越 i i i i + 1 i+1 i+1 的颜色区间的个数,那么合并后的区间的边界处 b i b_i bi 必然为0,且边界和 b i b_i bi 为0等价,所以就是求 b i b_i bi 中所有相邻两个0之间的区间最大值和。由于我们的修改包含区间加,不方便维护0的位置,但是有 b i ≥ 0 b_i\ge 0 bi0,所以再次转化为求所有相邻 b i b_i bi 的最小值之间的区间最大值和。

剩下的就很好办了,只需要set维护前驱后继,单点修改颜色数量,区间修改 b i b_i bi,线段树维护即可,复杂度 O ( ( n + q ) log ⁡ n ) O((n+q)\log n) O((n+q)logn)

代码

咕咕咕...

题解2

说说我考场上未打完的做法。

其实不需要正解这个转换,直接线段树维护区间合并也行。

把每个位置和它的前驱当成最小的区间,然后合并起来。线段树上合并的时候只需要考虑右儿子区间中最小的前驱,如果跨越中点则搜索其可以覆盖左儿子中的哪些区间的右端点,然后把他们合并为一个跨越中点的大区间即可。

按上面这段话的意思,貌似要用线段树套可持久化 t r e a p \rm treap treap (臭)之类的东西维护,常数极大还过不了空间。然而实际上,由于线段树上每个节点都记录了跨越中点的区间的信息,所以可以直接在线段树上往左儿子递归查找覆盖的区间,不需要另外的数据结构。

为了方便,你可能需要另一棵高效的线段树来快速查询区间内颜色数量最大值。

这个无脑做法的复杂度是 O ( n log ⁡ 2 n ) O(n\log^2n) O(nlog2n) 的,在强大的 L i n u x \rm Linux Linux 少爷机上直接跑过。

代码2

#include<cstdio>//JZM yyds!!
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<ctime>
#include<vector>
#include<queue>
#include<stack>
#include<map>
#include<set>
#define ll long long
#define uns unsigned
#define MOD 
#define MAXN 200005
#define INF 1e17
#define IF (it->first)
#define IS (it->second)
using namespace std;
inline ll read(){
	ll x=0;bool f=1;char s=getchar();
	while((s<'0'||s>'9')&&s>0)f^=(s=='-'),s=getchar();
	while(s>='0'&&s<='9')x=(x<<1)+(x<<3)+(s^48),s=getchar();
	return f?x:-x;
}
int pt[30],lp;
inline void print(ll x,char c='\n'){
	if(x<0)putchar('-'),x=-x;
	pt[lp=1]=x%10;
	while(x>9)x/=10,pt[++lp]=x%10;
	while(lp)putchar(pt[lp--]^48);
	putchar(c);
}
inline ll lowbit(ll x){return x&-x;}
int n,Q,a[MAXN],pr[MAXN];
int c[MAXN],d[MAXN];
set<int>st[MAXN];
set<int>::iterator it;
int p;
struct zkw{	//你需要另一棵高效的线段树
	int f[MAXN*3];
	inline void init(int n){
		for(p=1;p<n+2;p<<=1);
		for(int i=p+n+1;i>0;i--)f[i]=0;
	}
	inline void chg(int x,int y){
		for(f[p+x]=y,x=(p+x)>>1;x;x>>=1)
			f[x]=max(f[x<<1],f[x<<1|1]);
	}
	inline int query(int l,int r){
		int res=0;
		for(l=p+l-1,r=p+r+1;l^1^r;l>>=1,r>>=1){
			if(~l&1)res=max(res,f[l^1]);
			if(r&1)res=max(res,f[r^1]);
		}
		return res;
	}
}Z;
struct node{
	int l,r;ll z;node(){}
	node(int L,int R,ll Z){
		l=L,r=R,z=Z;
	}
}f[MAXN<<2],g[MAXN<<2];
ll s[MAXN<<2],sl[MAXN<<2],sr[MAXN<<2];
int mn[MAXN<<2];	//屎堆部分↓
inline node getsum(int x,int l,int r,int a){
	if(a>r)return node(r+1,r+1,0);
	if(l>=a)return node(l,r,s[x]);
	int mid=(l+r)>>1,c=f[x].r;
	node res;
	if(a<=c){
		res=node(f[x].l,r,f[x].z+s[x<<1|1]-sr[x]);
		if(a<f[x].l){
			node re=getsum(x<<1,l,mid,a);
			res=node(re.l,r,res.z+re.z-sl[x]);
		}
	}else res=getsum(x<<1|1,mid+1,r,a);
	return res;
}
inline void update(int x,int l,int r){
	f[x]=getsum(x<<1,l,(l+r)>>1,mn[x<<1|1]);
	sl[x]=f[x].z,sr[x]=g[x<<1|1].z;
	f[x].r=g[x<<1|1].r,f[x].z=Z.query(f[x].l,f[x].r);
	if(g[x<<1].l<f[x].l)g[x]=g[x<<1];
	else g[x]=f[x];
	s[x]=s[x<<1]+s[x<<1|1]+f[x].z-sl[x]-sr[x];
	mn[x]=min(mn[x<<1],mn[x<<1|1]);
}
inline void build(int x,int l,int r){
	if(l==r){
		f[x]=g[x]=node(l,r,Z.query(l,r));
		s[x]=f[x].z,sl[x]=sr[x]=0,mn[x]=pr[l];
		return;
	}int mid=(l+r)>>1;
	build(x<<1,l,mid),build(x<<1|1,mid+1,r);
	update(x,l,r);
}
inline void change(int x,int l,int r,int z){
	if(l==r){
		f[x]=g[x]=node(l,r,Z.query(l,r));
		s[x]=f[x].z,sl[x]=sr[x]=0,mn[x]=pr[l];
		return;
	}int mid=(l+r)>>1;
	if(z<=mid)change(x<<1,l,mid,z);
	else change(x<<1|1,mid+1,r,z);
	update(x,l,r);
}

inline void addc(int x,int y){
	if(!st[y].empty())Z.chg(*st[y].begin(),0);
	st[y].insert(x),it=st[y].find(x);
	Z.chg(*st[y].begin(),st[y].size());
	pr[x]=x;
	if(it!=st[y].begin())it--,pr[x]=*it,it++;
	change(1,1,n,x);
	it++;
	if(it!=st[y].end())pr[*it]=x,change(1,1,n,*it);
	if(*st[y].begin()!=x)change(1,1,n,*st[y].begin());
}
inline void delc(int x,int y){
	if(!st[y].empty())Z.chg(*st[y].begin(),0);
	it=st[y].find(x);
	int sf=0,p=0;
	it++;
	if(it!=st[y].end())sf=*it;
	it--;
	if(it!=st[y].begin())it--,p=*it,it++;
	pr[x]=x;
	if(sf){
		if(p>0)pr[sf]=p;
		else pr[sf]=sf;
	}
	st[y].erase(x);
	if(!st[y].empty())Z.chg(*st[y].begin(),st[y].size());
	change(1,1,n,x);
	if(sf>0)change(1,1,n,sf);
	if(!st[y].empty()&&*st[y].begin()!=x)
		change(1,1,n,*st[y].begin());
}
signed main()
{
	freopen("umi.in","r",stdin);
	freopen("umi.out","w",stdout);
	n=read(),Q=read();
	for(int i=1;i<=n;i++)a[i]=read();
	Z.init(n);
	for(int i=1;i<=n;i++){
		if(st[a[i]].empty())pr[i]=i;
		else pr[i]=*st[a[i]].rbegin();
		st[a[i]].insert(i);
		Z.chg(*st[a[i]].begin(),st[a[i]].size());
	}
	build(1,1,n);
	printf("%lld\n",n-s[1]);
	while(Q--){
		int x=read(),y=read();
		delc(x,a[x]);
		a[x]=y,addc(x,y);
		printf("%lld\n",n-s[1]);
	}
	return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值