海亮寄 7.8

前言

业精于勤荒于嬉,行成于思毁于随

正文(模拟赛)

卦象:大凶

模拟赛原题来源:COCI 2023

吐槽:我的补题时间呢???为什么要加一场模拟赛???本来想熬会夜学一下点分治的,教练组整这出???

(总有地上的生灵,敢于直面雷霆的威光服从教练组的安排)

感受:是 IOI 赛制,\(3\) 个小时 \(5\) 道题,分数 \(400/500\)。早上起来整个人的精神状态并不是特别好,有点晕,大脑可能没有完全开机。T1 人口普查,打字速度慢注定了没有拿到一血。隔壁墨鱼软软二人组在云落提交 T1 的时候,已经先后切掉 T1 和 T2 了,悲。后来开 T2,15 min 解决第二道人口普查题的代码。然而没有判断 corner case,一直处于 WA 的状态。瞪眼 20 min 无果,内心挣扎一会后大踏步地走向 T3,一眼不可做题,掉头走向 T4。T4 总感觉自己以前刷题刷到过类似的题目,所以很快有了正解的思路。这时候老师提示大家 T3 可能略有难度,建议优先开后面两道题目。听到老师的播报,只能说喜忧参半。喜是因为这证明了若干分钟以前放弃 T3 的决策是正确的。忧是因为许多人(包含绝大多数小登)都解决了前两题,然而云落依旧是可怜的 116pts。平复心情后,秒 T4,回去重构 T2,再切 T2。以上操作在 40min 内完成,算是追上了平均线。剩余 80 min 开 T5,审错题以为是分块板子题,上来就是一套根号组合拳。结果漏看一个条件,浪费 30 min。本来想打暴力的,因为 T3 和 T5 的暴力分数一共 114 分,性价比相当高。但注意到 T5 的特殊性质,突然灵光一闪,离线扫描线剩下的就是线段树板子。于是就有点贪了,想要 164 pts(T5 正解 + T3 暴力)。理想是丰满的,现实是骨感的。rz 线段树板子调了 40 min,原因是区间合并的重载运算符写锅了,和线段树建树部分初始化错误,最后遗憾只剩余 10 min 罚坐时间。总而言之,切了四个题,但是心惊胆战的,没有一个拿一血。并且最后有一点点决策失误,略有遗憾叭!

题解链接

T1

人口普查,枚举雪花中心即可,需要了解反斜杠的表达方式是 '\\'

T2

人口普查 \(\times 2\),广搜板子题

赛时前段可能不咋清醒,没有判无解的情况,后来重构后就 A 掉了

T3

机房里两个用脚维护的 dalao,一个树套树,一个 CDQ

STO 软软 & 火腿肠 Orz !!!

用的是老师讲题时的解法,没有用脚,开发大脑

由题意,有以下几点

  1. 题意等价转化为求

\[\sum_{x} \sum_{i} [i < x \land a_i > a_x \land a_x \text{是后缀} \min] \]

  1. 对于一个后缀 \(\min\) 值,其对答案的贡献是 \(i-a_i\)

  2. 一个元素成为后缀 \(\min\) 的时间,充要转化为在它后面且比它小的数消失时间的 \(\max\) 值(经典二维偏序问题,离线扫描线维护)

根据以上三点,大体流程就出来了

我们需要动态维护 \(i,a_i\) 以及提前离线预处理出成为后缀 \(\min\) 的时间

考虑一轮操作后如何计算答案

删除部分存在两种情况

  • 删除后缀 \(\min\),则剔除该后缀 \(\min\) 给的贡献

  • 删除非后缀 \(\min\),会对该元素以后的所有后缀 \(\min\) 造成贡献 \(-1\) 的操作

第一点是好做的(用 set 维护后缀 \(\min\) 值,lower_bound 查一下即可)

第二点需要动态维护一个元素后的后缀 \(min\) 个数

然后是计算与更新部分

可以根据已经处理出的元素成为后缀 \(\min\) 的时间表来完成贡献计算,计算式还是经典的 \(i-a_i\)

感觉可能细节较多,给一份丑陋的代码实现:

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define pii pair<int,int>
#define fi first
#define se second
#define mkp make_pair
#define vi vector<int>
#define pb push_back
#define lwbd lower_bound
using namespace std;
const int N=1e5+5;
int n,Q,a[N],q[N],tim[N];vi vec[N];
set<pii> S;
bool vis[N],mrk[N];
struct BIT{
    int c[N];
    inline int lb(int x){return x&(-x);}
    inline void add(int x,int v){
        for(int i=x;i<=n;i+=lb(i))c[i]+=v;
        return;
    }
    inline int ask(int x){
        int res=0;
        for(int i=x;i;i-=lb(i))res+=c[i];
        return res;
    }
    inline int qry(int l,int r){return ask(r)-ask(l-1);}
}bit1,bit2,bit3;
struct Segment_Tree{
    struct node{int l,r,mx;}tr[N<<2];
    inline void pushup(int u){
        tr[u].mx=max(tr[u<<1].mx,tr[u<<1|1].mx);
        return;
    }
    inline void build(int u,int l,int r){
        tr[u].l=l,tr[u].r=r;
        if(l==r){tr[u].mx=0;return;}
        int mid=(l+r)>>1;
        build(u<<1,l,mid),build(u<<1|1,mid+1,r);
        pushup(u);
        return;
    }
    inline void modify(int u,int pos,int v){
        int l=tr[u].l,r=tr[u].r;
        if(l==r){tr[u].mx=v;return;}
        int mid=(l+r)>>1;
        if(pos<=mid)modify(u<<1,pos,v);
        else modify(u<<1|1,pos,v);
        pushup(u);
        return;
    }
    inline int query(int u,int ql,int qr){
        int l=tr[u].l,r=tr[u].r;
        if(ql<=l&&qr>=r)return tr[u].mx;
        int mid=(l+r)>>1,res=0;
        if(ql<=mid)res=max(res,query(u<<1,ql,qr));
        if(qr>mid)res=max(res,query(u<<1|1,ql,qr));
        return res;
    }
}sgt;
signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    cin>>n>>Q;
    for(int i=1;i<=n;i++)cin>>a[i];
    for(int i=1;i<=n;i++)tim[i]=Q+1;
    for(int i=1;i<=Q;i++){cin>>q[i];tim[q[i]]=i;}
    sgt.build(1,1,n);
    for(int i=n;i>=1;i--){
        int x=sgt.query(1,1,a[i]-1);
        vec[x].pb(i);
        sgt.modify(1,a[i],tim[i]);
    }
	S.insert(mkp(0,0)),S.insert(mkp(n+1,n+1));
    int sum=0;
    for(int i:vec[0]){
        bit3.add(i,1);vis[i]=true;S.insert(mkp(a[i],i));
        sum+=(i-a[i]);
    }
	cout<<sum+n<<' ';
    for(int i=1;i<=n;i++)bit1.add(i,1),bit2.add(a[i],1);
    for(int i=1;i<=Q;i++){

        // del

        mrk[q[i]]=true;
        if(!vis[q[i]])sum-=(bit3.qry(q[i]+1,(--S.lwbd({a[q[i]],0}))->se));
        else{
            bit3.add(q[i],-1);
            S.erase(mkp(a[q[i]],q[i]));
            sum-=(bit1.ask(q[i])-bit2.ask(a[q[i]]));
        }
        bit1.add(q[i],-1),bit2.add(a[q[i]],-1);

        // cal

        for(int x:vec[i]){
            if(mrk[x])continue;
            bit3.add(x,1);vis[x]=true;S.insert({mkp(a[x],x)});
            sum+=(bit1.ask(x)-bit2.ask(a[x]));
        }
        cout<<sum+(n-i)<<' ';
    }
    cout<<'\n';
    return 0;
}

T4

做过类似的博弈论题,也做过类似的区间结构题,正解基本上呼之欲出

\(f_{l,r}\) 表示两人对区间 \([l,r]\) 进行游戏的最大分数差,容易发现转移形如决策一个边界条件。首先 \(a_l\)(或者 \(a_r\))先判断是否可以加分,其次再判断能否拉开分数差

(其实观察性质发现,分数差总是 \(0/1\)?,这个结论是瞎猜的,未必正确)

那么一开始统计处分数和,在结合 \(f_{1,n}\),就可以分别求出先后手的分数了

T5

读错题 \(+\) 选用错误的算法 \(+\) 正确代码的降智错误

全局查询已经提示我们离线扫描线了,容易发现按海平面上升顺序扫描线并不好做(极长连续段计数问题不好撤销),所以考虑按海平面下降顺序扫描线。

《线段树随便维护即可》

小结

学 T3 去咯!明天休息一天!好耶!

Upd. 诶?明天休息半天?不耶……

后记

世界孤立我任它奚落

完结撒花!

posted @ 2025-07-08 16:01  sunxuhetai  阅读(12)  评论(0)    收藏  举报