暂未找到原题链接
题目描述
给定一个长度为 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转序列问题都没想到
第二题不绑点,直接先骗分+乱搞,结果y0
爆CE
了
就这第三题好不容易想到了 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 bi≥0,所以再次转化为求所有相邻 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;
}