JOI 2020 Final 火灾

URL

https://loj.ac/problem/3256

简要题意

有一个长度为n的数字序列s[1...n]

再接下来的n个时刻,按照从后往前的顺序,每个s[i]都会和s[i-1]取max(在n+1个时刻后序列肯定不会改变了,每个位置都是前缀最大值)

多次询问(t,l,r),表示第t个时刻[l,r]内数字的和

n,q 200000

解法

可以看看官方题解 https://www.ioi-jp.org/joi/2019/2020-ho/2020-ho-t5-review.pdf 的图

考虑每一个位置的数字在(n+1)*n的矩阵中的连通块(如果在取max时两个数相等,我们认为新的max值来自后面的那个)。

对每个数字用单调栈算出:

  1. pl[i]:左边第一个比它大的位置,不存在的话设为-1
  2. pr[i]:右边第一个不比它小的位置

按照pl[i]分两种情况:

  1. 左边的每个数都不超过它:覆盖范围是以pr[i]-1为右边界的矩形,扣去一个以pr[i]-1为有边界的直角三角形。
  2. 左边存在大于它的:覆盖范围是一个平行四边形,可以差分成三个直角三角形。

然后离线+树状数组即可。

注意三角形得拆成一个后缀直角梯形,扣掉一个后缀矩形。具体实现时可以把梯形和矩形的贡献分开来统计,统计梯形的贡献时每次把数轴整体右移一位。

实现

#include <bits/stdc++.h>
using namespace std;

#define rng(i,a,b) for(int i=int(a);i<int(b);i++)
#define rep(i,b) rng(i,0,b)
#define gnr(i,a,b) for(int i=int(b)-1;i>=int(a);i--)
#define per(i,b) gnr(i,0,b)
#define pb push_back
#define eb emplace_back
#define bg begin()
#define ed end()
#define all(x) (x).bg,(x).ed
#define si(x) int((x).size())
#define mp make_pair
#define a first
#define b second

#ifdef LOCAL
#define dmp(x) cerr<<__LINE__<<" "<<#x<<" "<<x<<endl
#else
#define dmp(x) void(0)
#endif

template<class t,class u> void chmax(t&a,u b){if(a<b)a=b;}
template<class t,class u> void chmin(t&a,u b){if(b<a)a=b;}

template<class t> using vc=vector<t>;
template<class t> using vvc=vc<vc<t>>;

using ll=long long;
using uint=unsigned int;
using pi=pair<int,int>;
using vi=vc<int>;

int topbit(signed t){
    return t==0?-1:31-__builtin_clz(t);
}
int topbit(ll t){
    return t==0?-1:63-__builtin_clzll(t);
}

template<class t>
struct BIT{
    vc<t> buf;
    int s;
    BIT(int s_=0){init(s_);};
    void init(int s_){buf.assign(s=s_,0);}
    void add(int i,t v){
        for(;i<s;i+=(i+1)&(-i-1))
            buf[i]+=v;
    }
    t get(int i){
        t res=0;
        for(;i>=0;i-=(i+1)&(-i-1))
            res+=buf[i];
        return res;
    }
    t sum(int b,int e){
        return get(e-1)-get(b-1);
    }
    /*int kth(int k){
        int res=0;
        for(int i=topbit(s);i>=0;i--){
            int w=res+(1<<i);
            if(w<=s&&buf[w-1]<=k){
                k-=buf[w-1];
                res=w;
            }
        }
        return res;
    }*/
};

template<class t>
struct X{
    BIT<t> a,b;
    int s;
    X(int s_){
        s=s_;
        a.init(s);
        b.init(s);
    }
    void add(int i,t v){
        a.add(i,v);
        b.add(i,-i*v);
    }
    void add(int l,int r,t v){
        add(l,v);
        add(r,-v);
    }
    t get(int i){
        return i*a.get(i-1)+b.get(i-1);
    }
    t get(int l,int r){
        return get(r)-get(l);
    }
};

struct trape{
    int i,v;
};
struct rect{
    int l,r,v;
};
struct query{
    int l,r,i;
};

const int nmax=2.1e5;
const int qmax=2.1e5;
int n,q;
int s[nmax];
int pl[nmax],pr[nmax];

vc<trape> etrape[nmax];
vc<rect> erect[nmax];

void addtrape(int i,int h,int v){
    etrape[0].pb(trape{i,v});
    etrape[h].pb(trape{i+h,-v});
}

void addrect(int l,int r,int h,int v){
    if(l==r)return;
    erect[0].pb(rect{l,r,v});
    erect[h].pb(rect{l,r,-v});
}

void addtri(int l,int r,int v){
    if(l==r)return;
    addtrape(l,r-l,v);
    addrect(r,n,r-l,-v);
}

vc<query> qs[nmax];
ll res[qmax];

int main(){
    ios::sync_with_stdio(0);
    cin.tie(0);

    cin>>n>>q;
    rep(i,n)cin>>s[i];

    {
        vi st;
        rep(i,n){
            while(si(st)&&s[st.back()]<=s[i]){
                pr[st.back()]=i;
                st.pop_back();
            }
            if(si(st))pl[i]=st.back();
            else pl[i]=-1;
            st.pb(i);
        }
        while(si(st)){
            pr[st.back()]=n;
            st.pop_back();
        }
    }

    rep(i,n){
        if(pl[i]==-1){
            addrect(i,pr[i],n+1,s[i]);
            addtri(i+1,pr[i],-s[i]);
        }else{
            addtri(pl[i]+1,pr[i],s[i]);
            addtri(pl[i]+1,i,-s[i]);
            addtri(i+1,pr[i],-s[i]);
        }
    }

    X<ll> xtrape(2*n+10);
    X<ll> xrect(n+10);

    rep(i,q){
        int t,l,r;cin>>t>>l>>r;l--;
        qs[t].pb(query{l,r,i});
    }

    int shift=n+5;
    rep(t,n+1){
        for(auto et:etrape[t])
            xtrape.add(et.i+shift,et.v);
        for(auto er:erect[t])
            xrect.add(er.l,er.r,er.v);
        auto get=[&](int l,int r)->ll{
            return xrect.get(l,r)+xtrape.get(l+shift,r+shift);
        };
        for(auto dt:qs[t])
            res[dt.i]=get(dt.l,dt.r);
        shift--;
    }

    rep(i,q)
        cout<<res[i]<<'\n';
}

原文地址:https://www.cnblogs.com/iefnah06/p/12309255.html

时间: 2024-11-03 22:40:00

JOI 2020 Final 火灾的相关文章

【2018.9.20】JOI 2017 Final T2「準急電車 / Semiexpress」

日文原题 题目描述 JOI 铁路公司是 JOI 国唯一的铁路公司. 在某条铁路沿线共有 $N$ 座车站,依次编号为 $1...N$. 目前,正在服役的车次按照运行速度可分为两类:高速电车(简称快车)与普通电车(简称慢车). 慢车每站都停.乘慢车时,对于任意一座车站 $i(1?i<N)$,车站 $i$ 到车站$ i+1$ 用时均为 $A$. 快车只在车站 $S_1, S_2, \ldots, S_M$?? 停车 $(1=S_1<S_2<\cdots<S_M=N)$.乘快车时,对于任意

【2018.9.20】JOI 2017 Final T3「JOIOI 王国 / The Kingdom of JOIOI」

题目描述 为了兼顾表意清楚与简洁,我翻译时脑补了 RJOIR_{JOI}R?JOI?? 和 RIOIR_{IOI}R?IOI??,所以不要问我为啥原题找不到…… JOIOI 王国是一个 HHH 行 WWW 列的长方形网格,每个 1×11\times 11×1 的子网格都是一个正方形的小区块.为了提高管理效率,我们决定把整个国家划分成两个省 JOI 和 IOI . 我们定义,两个同省的区块互相连接,意为从一个区块出发,不用穿过任何一个不同省的区块,就可以移动到另一个区块.有公共边的区块间可以任意移

[JOI 2015 Final]分蛋糕 2

link 试题分析 容易发现性质,选择的是一段区间,但是贪心无法去维护这件事情,所以考虑$dp$,且我们只要去设计关于$JOI$的选择. 设$dp(i,j)$为现在要在$[l,r]$区间内选择,然后就可以随便写了. #include<iostream> #include<cstring> #include<cstdio> #include<algorithm> #define int long long using namespace std; inline

【2018.10.1】「JOI 2014 Final」年轮蛋糕

题面 一看到求“最小值的最大值”这种问题,就能想到二分了. 二分答案,然后判然后把环展开成2倍长度的链,先钦定一个起点, 原文地址:https://www.cnblogs.com/scx2015noip-as-php/p/9734669.html

Libre OJ P2332「JOI 2017 Final」焚风现象【差分思想】By cellur925

题目传送门 这道题开始看起来会很晕...\(qwq\).首先我们要明确题目中的海拔&&温度.温度是受海拔影响的,每次改变的是海拔,我们求的是温度. 我们开始读入的时候便可以处理出开始\(N\)位置的温度以及各个位置的海拔差.每次读入影响的是一段区间,区间内的相对海拔是不变的因此温度也不会变.只有区间的边界可能受到影响.因此我们只要处理边界就行了:这便是差分的思想. 比如有\([l,r]\)区间需要处理,那么我们把\(l\)位置的原答案减去,把\(l\)位置的海拔改变,并加上新答案.再对\(

[JOI 2014 Final]IOI 馒头

link 试题分析 我们发现若是要选馒头的话则应该从馒头售价高的先装. 并且若要选择包装盒时应该选择装x个最小的时候.所以只需要贪心$+$背包即可. #include<iostream> #include<cstring> #include<cstdio> #include<algorithm> using namespace std; inline int read(){ int f=1,ans=0;char c=getchar(); while(c<

loj#2334 「JOI 2017 Final」JOIOI 王国

分析 二分答案 判断左上角是否满足 为了覆盖所有范围 我们依次把右下角,左上角,右上角移动到左上角 代码 #include<bits/stdc++.h> using namespace std; int a[2010][2010],n,m,Ans=1e9+7,mx,mn=1e9+7; inline bool ck(int x){ int i,j,k,l=mn+x,r=mx-x,lim=m; for(i=1;i<=n;i++){ for(j=1;j<=lim;j++) if(a[i]

loj 2759「JOI 2014 Final」飞天鼠

loj 这题有在一棵树上上升或者下降的操作,稍加分析后可以发现上升操作如果不是一定要做(指高度不足以到下一棵树或者是最后到达\(n\))就不做,下降操作也是如果不是一定要做(指到达下一棵树时高度过高)就不做,因为如果提前做了,可能会导致后面要浪费一些步数使得移动合法.然后这个移动过程就会分成两段,先是一直移动或者下降,不用上升,然后会每次上升再移动,一直到终点 先看前一段的移动,如果移动的时候正好能移到下一棵树就直接移,如果移的时候高度过高就往下移一点直到能正好移动到下一棵树上.这里对每个点记\

loj 3014「JOI 2019 Final」独特的城市

loj 我本来是直接口胡了一个意思一样的做法的,但是因为觉得有点假+实现要用并查集(?)就卡了好一会儿... 对于一个点\(x\)来说,独特的点一定在它的最长链上,如果有独特的点不在最长链上,那么最长链上一定有和他到\(x\)距离相同的点,矛盾 然后对于一个点,最长链端点一定可以是直径的两端点之一,所以如果我们分别以树的直径的两端点为根进行dfs,那么一个点在其中一次dfs中,独特的点都会在到根的路径上,所以我们用栈维护到根的点,然后不同颜色数开桶来维护,每次压栈或弹栈时改变桶内元素个数,然后根