题解:P9910 [COCI 2023/2024 #2] Dizalo
题解 : P9910 Dizalo
自认为比较详细。
题目大意
给定一个
题目分析
先不考虑动态问题,观察到重排的策略必然是顺序排序,这样显然不劣,考虑如何刻画。
当进行到值为
贡献计算是这样的:每个人都必须离开一次,总贡献是
这里停一下,发现我们对原问题的转化已经是足够的了,已经可以拿到非平凡部分分了,随便用树状数组这类东西来维护这样的偏序关系就能做到
~模拟赛场上本蒟蒻写了个分块加二分的写法,不幸没调出来。~
考虑第一个条件的否定
考虑维护所有后缀最小值的位置,你把所有后缀最小值拎出来类似单调栈,每个元素只会至多被加入或删除
首先肯定不能每次加完点后都去对每个后缀最小值去求一遍其权值,复杂度肯定不对,考虑去维护增量。
首先就是加入一个点后它本身可能成为后缀最小值,那怎么求它的权值呢?你发现
其次你会发现,加入这个点可能让它前面的一些点不再是后缀最小值,前面分析发现暴力做复杂度都是对的,你用 set 一类的东西能做到删除单个元素
最后,对于一些已经是后缀最小值的点,其权值可能有变化,还是那个式子,你考虑维护一个增量,就是有多少后缀最小值的权值会改变,同样把所有后缀最小值的权值和位置分别开树状数组来维护这个增量。
总复杂度
:::info[CODE]
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define lowbit(x) ((x)&(-(x)))
inline int read(){}
inline void write(int x){}
const int N=1e5+100;
int n,a[N],op[N],del[N],m,res[N],ans;
struct BIT{
//树状数组
}s,pos,val,mipos,mival;
bool check(int p){
int cnt=s.qry(n)-s.qry(p);
if(!cnt) return 1;
int l=p+1,r=n,mid;
while(l<r){
mid=l+r>>1;
if(s.qry(p)+1<=s.qry(mid)) r=mid;
else l=mid+1;
}
if(a[p]<a[l]) return 1;
else return 0;
}
inline void add(int p){
ans++;
int tot=s.qry(p-1);
while(tot){
int l=1,r=p-1,mid;
while(l<r){
mid=l+r>>1;
if(s.qry(mid)==tot) r=mid;
else l=mid+1;
}
if(a[l]>a[p]){
ans-=val.qry(n)-val.qry(a[l])-pos.qry(n)+pos.qry(l);
s.add(l,-1);
mipos.add(l,-1);
mival.add(a[l],-1);
tot--;
}else{
break;
}
}
if(check(p)){
s.add(p,1);
mipos.add(p,1);
mival.add(a[p],1);
ans+=val.qry(n)-val.qry(a[p])-pos.qry(n)+pos.qry(p);
}
val.add(a[p],1);
pos.add(p,1);
ans+=mival.qry(a[p]-1);
ans-=mipos.qry(p-1);
}
signed main(){
n=read();m=read();
for(int i=1;i<=n;i++) a[i]=read();
for(int i=1;i<=m;i++){
op[i]=read();del[op[i]]=1;
}
for(int i=1;i<=n;i++){
if(!del[i]) add(i);
}
res[m]=ans;
for(int i=m;i>=1;i--){
add(op[i]);
res[i-1]=ans;
}
for(int i=0;i<=m;i++){
write(res[i]);putchar(' ');
}
return 0;
}
:::