CF387E George and Cards 题解
toolong114514
·
·
题解
CF387E George and Cards 题解
题面大意
传送门。
解题思路
考虑一种贪心的策略:从小到大删数。
由于较大的数总是会对较小的数产生区间的贡献,所以要让较大的数尽可能晚删。如果先删了较大的数,那么答案一定不会更优。
将整个数组从小到大排序,如果当前是要删的数,那么计算出向左最长的区间长,以及向右最长的区间长,加上自己所占的的区间长(值显然为 1),就是这个数的贡献。加完后删掉这个数。最后全部加起来即可。
没那么好的方法
考虑写一颗维护区间最值和区间和的线段树,并记 \operatorname{cnt}(x,y) 为当前区间 [x,y] 剩下的数字个数。
对于当前的点 t,每次分别向左向右二分(在固定一个区间的端点时),找到最小的 l\in[1,t],满足 \min\{a_l,a_{l+1},\cdots,a_t\}\geqslant a_t,找到最大的 r\in[t,n],满足 \min\{a_t,a_{t+1},\cdots,a_r\geqslant a_t\},就可以计算出想要的答案,即 \operatorname{cnt}(l,r)。算完后在线段树上删去当前的 a_t
二分时每一次 check 时间复杂度为 O(\log n),即一次二分的总体时间复杂度为 O(\log^2{n})。线段树的一次删除操作为 O(\log n)。总共要做 O(n) 次二分,即上述做法总的时间复杂度为 O(n\log^2 n)。
做法比较优秀,但在 1\le k\le n\le10^6 的数据范围和 CF 造的的数据下,会光荣地 Time limit exceeded on test 8。
正解
考虑对刚才的做法进行优化。
我们发现,以删除为主的线段树只能在其上二分,已无优化可能,需要换一个方向:删数变成加数。
先开一颗(权值?)线段树,维护区间最值。
还是顺序扫一遍,记当前数为 a_t。
如果 a_t 不需要删除,那么以 t 为下标,将 t 插入线段树中。
若 a_t 要删除,发现此时线段树里都是限制贡献的数,那么直接找到线段树里最大的 1\le l\le t,最小的 t\le r\le n,当前贡献即为 \operatorname{cnt}(l,r),\operatorname{cnt} 函数的意义同上。最后在线段树里删掉 a_t,维护一下 \operatorname{cnt} 即可。
每一次删除操作,涉及的线段树操作是 O(\log{n}) 的,一共要删 O(n) 个数,总的时间复杂度即为 O(n\log{n}),可以通过本题。
答案记得要开 long long!
卡常
当然,直接这么写常数太大,会被卡 TLE,需要卡常(听说将权值线段树改为 STL 容器就能过)。
我写的代码卡常内容如下:
- 开
O4 优化。
- 加火车头指令集。
- 手写
std::max 和 std::min 函数。
- 写快读。
注:CF 允许编译代码中的指令集和优化指令。
如果你有什么更好的卡常方案,欢迎在本文的讨论区分享。
逆天的运行时间:
参考代码
$O(n\log{n})$ 的做法(带卡常):
```cpp
#include<algorithm>
#include<iostream>
#pragma GCC optimize(4)
#pragma GCC target("avx")
#pragma GCC optimize("Ofast")
#pragma GCC optimize("inline")
#pragma GCC optimize("-fgcse")
#pragma GCC optimize("-fgcse-lm")
#pragma GCC optimize("-fipa-sra")
#pragma GCC optimize("-ftree-pre")
#pragma GCC optimize("-ftree-vrp")
#pragma GCC optimize("-fpeephole2")
#pragma GCC optimize("-ffast-math")
#pragma GCC optimize("-fsched-spec")
#pragma GCC optimize("unroll-loops")
#pragma GCC optimize("-falign-jumps")
#pragma GCC optimize("-falign-loops")
#pragma GCC optimize("-falign-labels")
#pragma GCC optimize("-fdevirtualize")
#pragma GCC optimize("-fcaller-saves")
#pragma GCC optimize("-fcrossjumping")
#pragma GCC optimize("-fthread-jumps")
#pragma GCC optimize("-funroll-loops")
#pragma GCC optimize("-fwhole-program")
#pragma GCC optimize("-freorder-blocks")
#pragma GCC optimize("-fschedule-insns")
#pragma GCC optimize("inline-functions")
#pragma GCC optimize("-ftree-tail-merge")
#pragma GCC optimize("-fschedule-insns2")
#pragma GCC optimize("-fstrict-aliasing")
#pragma GCC optimize("-fstrict-overflow")
#pragma GCC optimize("-falign-functions")
#pragma GCC optimize("-fcse-skip-blocks")
#pragma GCC optimize("-fcse-follow-jumps")
#pragma GCC optimize("-fsched-interblock")
#pragma GCC optimize("-fpartial-inlining")
#pragma GCC optimize("no-stack-protector")
#pragma GCC optimize("-freorder-functions")
#pragma GCC optimize("-findirect-inlining")
#pragma GCC optimize("-fhoist-adjacent-loads")
#pragma GCC optimize("-frerun-cse-after-loop")
#pragma GCC optimize("inline-small-functions")
#pragma GCC optimize("-finline-small-functions")
#pragma GCC optimize("-ftree-switch-conversion")
#pragma GCC optimize("-foptimize-sibling-calls")
#pragma GCC optimize("-fexpensive-optimizations")
#pragma GCC optimize("-funsafe-loop-optimizations")
#pragma GCC optimize("inline-functions-called-once")
#pragma GCC optimize("-fdelete-null-pointer-checks")
using namespace std;
const int INF=1e9+10;
const int N=1e6+10;
int b[N];
bool vst[N];
int n,k,cnt;
long long ans;
struct ccf{
int pos,val;
}p[N];
bool cmp(ccf pre,ccf nxt){
return pre.val<nxt.val;
}
int tmax(int x,int y){
return (x>y)?(x):(y);
}
int tmin(int x,int y){
return (x<y)?(x):(y);
}
struct node{
int l,r,sum,maxn,minn;
}tree[4*N];
void push_up(int pos){
tree[pos].sum=tree[pos*2].sum+tree[pos*2+1].sum;
tree[pos].maxn=tmax(tree[pos*2].maxn,tree[pos*2+1].maxn);
tree[pos].minn=tmin(tree[pos*2].minn,tree[pos*2+1].minn);
}
void build(int pos,int lft,int rgt){
tree[pos].l=lft;
tree[pos].r=rgt;
if(lft==rgt){
tree[pos].sum=1;
tree[pos].maxn=-INF;
tree[pos].minn=INF;
return;
}
int mid=(lft+rgt)/2;
build(pos*2,lft,mid);
build(pos*2+1,mid+1,rgt);
push_up(pos);
}
void upd1(int pos,int dot){
if(dot<tree[pos].l||tree[pos].r<dot) return;
if(tree[pos].l==tree[pos].r){
tree[pos].sum=0;
return;
}
upd1(pos*2,dot);
upd1(pos*2+1,dot);
push_up(pos);
}
void upd2(int pos,int dot,int x){
if(dot<tree[pos].l||tree[pos].r<dot) return;
if(tree[pos].l==tree[pos].r){
tree[pos].maxn=tree[pos].minn=x;
return;
}
upd2(pos*2,dot,x);
upd2(pos*2+1,dot,x);
push_up(pos);
}
int ask1(int pos,int lft,int rgt){
if(rgt<tree[pos].l||tree[pos].r<lft||lft>rgt) return 0;
if(lft<=tree[pos].l&&tree[pos].r<=rgt) return tree[pos].sum;
return ask1(pos*2,lft,rgt)+ask1(pos*2+1,lft,rgt);
}
int ask2(int pos,int lft,int rgt){
if(rgt<tree[pos].l||tree[pos].r<lft) return -INF;
if(lft<=tree[pos].l&&tree[pos].r<=rgt) return tree[pos].maxn;
return tmax(ask2(pos*2,lft,rgt),ask2(pos*2+1,lft,rgt));
}
int ask3(int pos,int lft,int rgt){
if(rgt<tree[pos].l||tree[pos].r<lft) return INF;
if(lft<=tree[pos].l&&tree[pos].r<=rgt) return tree[pos].minn;
return tmin(ask3(pos*2,lft,rgt),ask3(pos*2+1,lft,rgt));
}
inline void read(int &x) {
x=0;int f=1;
char s=getchar();
for(;s<'0'||s>'9';s=getchar()) f=s=='-'?-f:f;
for(;s>='0'&&s<='9';s=getchar()) x=x*10+s-'0';
x*=f;
}
signed main(){
ios::sync_with_stdio(false);
read(n);read(k);
if(n==k){
cout<<"0";
return 0;
}
for(int i=1;i<=n;i++){
read(p[i].val);
p[i].pos=i;
}
for(int i=1;i<=k;i++){
read(b[i]);
vst[b[i]]=true;
}
build(1,1,n);
sort(p+1,p+n+1,cmp);
for(int i=1;i<=n;i++){
if(vst[p[i].val]){
upd2(1,p[i].pos,p[i].pos);
}else{
int tmp=-INF;
if(p[i].pos>1) tmp=ask2(1,1,p[i].pos-1);
if(tmp==-INF) tmp=0;
ans+=ask1(1,tmp+1,p[i].pos-1);
tmp=INF;
if(p[i].pos<n) tmp=ask3(1,p[i].pos+1,n);
if(tmp==INF) tmp=n+1;
ans+=ask1(1,p[i].pos+1,tmp-1);
ans++;
upd1(1,p[i].pos);
}
}
cout<<ans;
return 0;
}
```
[本代码可以通过 CF 的评测。](https://www.luogu.com.cn/record/162994392)
Written by [toolong114514](www.luogu.com/user/477821) on 2024/6/24.