「XXOI 2018」暑假时在做什么?有没有空?可以来学物理吗?题解

写在前面

本题在网上有且仅有一篇题解,并且没有把我讲懂,我和 ccy 一起讨论了一下,理论上得出了本题的解法。
题面:「XXOI 2018」暑假时在做什么?有没有空?可以来学物理吗?

Sol:

本题要求,对于每一个点 i,求一段长度在 R-L+1 之间的连续区间, 且经过点 i 的最大值。点有负权

如果本题没有负权,可以贪心切掉。但出现了负权,不妨采取分治的思想,求出两字区间相互的影响。

假设现在递归到的区间为 (l,r),先递归解决区间 (l,mid) 和区间 (mid+1,r)。
以 (l,mid) 为例,对于每个 i 属于 (l,mid),根据递归的性质,(l,mid) 这段区间中能对 i 产生贡献的一定已经计算完了,现在能对 i 产生贡献的一定是在 (mid+1,r) 这段区间内的。

首先,先假设没有越界的情况,令 x=i+L-1, y=i+R-1。需要求出 前缀和数组在这个范围内的最大值,可以用 ST 表维护。
其次,因为我们规定 (x,y) 一定完整包含于 (mid+1,r),就令 x=max(x,i+L-1),y=min(y,i+R-1) 即可,若出现不合法的 (x,y),返回无穷即可。
最后再考虑一下,我们所求的区间,一定是以 i 为左端点的,但很明显,i 不一定需要是左端点,就记一个变量 pre ,代表从 (l,i-1) 中经过 i 这个点的答案最大值,那 ans[i] 就赋值为 max(ans[i],pre).

Done.

Code:

点击查看代码
#include <bits/stdc++.h>
using namespace std;
template<class _ForwardIterator>
void print(_ForwardIterator first, _ForwardIterator last) {
    if (first != last) {
        do {
            (cout << (*first)).put(' ');
        } while (++first != last);
    }

    cout.put('\n');
}
typedef unsigned char uchar;
typedef unsigned short ushort;
typedef unsigned int uint;
typedef long long LL;
typedef unsigned long long ULL;
constexpr LL NINF = 0xc0c0c0c0c0c0c0c0;
constexpr uint N = 100005U;
LL ans[N];
uint Mylog2[N];
int a[N];
LL pre_sum[N], suf_sum[N];
LL pre_sum_max[N][17U], suf_sum_max[N][17U];
uint n, L, R;
void init() {
    for (uint i(1U), log2v(-1); i <= n; ++i) {
        if ((i & (-i)) == i)
            ++log2v;

        Mylog2[i] = log2v;
    }

#define len (1U<<k)
#define half_len (1U<<(k-1U))

    for (uint i(1U); i <= n; ++i)
        pre_sum[i] = pre_sum[i - 1U] + a[i];

    for (uint i(1U); i <= n; ++i)
        pre_sum_max[i][0U] = pre_sum[i];

    for (uint k(1U); len <= n; ++k) {
        for (uint i(1U); i + len - 1U <= n; ++i)
            pre_sum_max[i][k] = max(pre_sum_max[i][k - 1U], pre_sum_max[i + half_len][k - 1U]);
    }

    for (uint i(n); i; --i)
        suf_sum[i] = suf_sum[i + 1U] + a[i];

    for (uint i(1U); i <= n; ++i)
        suf_sum_max[i][0U] = suf_sum[i];

    for (uint k(1U); len <= n; ++k) {
        for (uint i(1U); i + len - 1U <= n; ++i)
            suf_sum_max[i][k] = max(suf_sum_max[i][k - 1U], suf_sum_max[i + half_len][k - 1U]);
    }

#undef len
#undef half_len
}
inline LL Query_pre_sum_max(uint l, uint r) {
    uint k(Mylog2[r - l + 1U]);
    return max(pre_sum_max[l][k], pre_sum_max[r - (1U << k) + 1U][k]);
}
inline LL Query_suf_sum_max(uint l, uint r) {
    uint k(Mylog2[r - l + 1U]);
    return max(suf_sum_max[l][k], suf_sum_max[r - (1U << k) + 1U][k]);
}
void solve(const uint l, const uint r) {
    if (l == r) {
        if (L == 1) // 如果区间长可以为1,那单点也是可以对答案产生贡献的
            ans[l] = a[l];

        return ;
    }

    const uint mid((l + r) >> 1U);
    // 分治处理
    // 如果左右区间内部都递归处理完,而且对于当前区间与其他相邻区间互相的贡献也会在更上层的递归中被处理
    // 那我们就只用考虑计算穿过中间点的区间对两边的点的答案的贡献
    // 正确性参考线段树的正确性

    // 因为需要的信息我们用ST表已处理完了,所以solve左区间,solve右区间,计算当前区间的答案这三个操作可以乱序
    // 但有的题要利用递归来处理子区间的信息以计算当前区间的答案,顺序就有自己的要求
    solve(l, mid);
    solve(mid + 1U, r);

    // 现在我们先考虑左区间,右区间同理
    // 只有跨过mid的区间会对i号点产生贡献,我们来计算一下这个贡献
    // 首先,对于位于左区间最左边的点来说,一定是以该点为左端点,以[i+L-1,i+R-1]中的点为右端点的区间会对其产生贡献
    // 用ST表预处理前缀的max,那i点就应该取max(ans[i],max(pre_sum[i+L-1,i+R-1])-pre_sum[i-1])(前缀和公式)
    // 但是因为包含于左区间内的区间我们已经处理完了,我们钦定只有右端点在右区间我们才计算贡献,即右端点rr应属于[i+L-1,i+R-1]和[mid+1,r]的交集
    // 接下来我们考虑下一个点j号点,可以发现,对j号点来说,对它产生贡献的区间为 该j为左端点,以[j+L-1,j+R-1]中的点为右端点的区间 和 对i号点产生贡献的区间
    // 我们要在这两堆区间中取最大值
    // 该j为左端点,以[j+L-1,j+R-1]中的点为右端点的区间的贡献的最大值可以同理求
    // 对i号点产生贡献的区间的最大值我们开一个pre_max记录一下
    // 我们就可以算对j号点的贡献了
    // 对下一个点k号
    // 产生贡献的区间为 该k为左端点,以[k+L-1,k+R-1]中的点为右端点的区间 和 对i号点产生贡献的区间 , 对j号点产生贡献的区间
    // 我们发现后面就是一个前面枚举的点对应的区间贡献的最大值
    // 每次都更新记录一下就好了
    // 同时,我们也可以发现,钦定右端点在右区间我们才计算贡献,就可以保证前面算过的值最大的区间一定包含当前枚举到的点,是可以对当前枚举到的点产生贡献的
    // 而且可以保证该区间一定是前面当中最优的,而包含于左区间内的区间我们已经在下面的递归中将其统计入ans[i]了,所以正确性是保证的
    LL pre_max(NINF);

    for (uint i(l); i <= mid; ++i) {
        uint ll(max(i + L - 1U, mid + 1U)), rr(min(i + R - 1U, r));

        if (ll <= rr)
            pre_max = max(pre_max, Query_pre_sum_max(ll, rr) - pre_sum[i - 1U]);

        ans[i] = max(ans[i], pre_max);
    }

    // 右区间的计算同理
    // 该i为右端点,以[i-R+1,i-L+1]中的点为左端点的区间会对其产生贡献的最大值为
    // max(suf_sum[i-R+1,i-L+1]-suf_sum[i+1U]
    // 再用ST表维护后缀的max
    // 只需要倒着计算贡献,记录一个suf_max即可
    LL suf_max(NINF);

    for (uint i(r); i > mid; --i) {
        int ll(max(int(i - R + 1U), int(l))), rr(min(int(i - L + 1U), int(mid)));

        if (ll <= rr)
            suf_max = max(suf_max, Query_suf_sum_max(ll, rr) - suf_sum[i + 1U]);

        ans[i] = max(ans[i], suf_max);
    }
}
int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(nullptr);
    // freopen("1.in","r",stdin);
    // freopen("1.out","w",stdout);
    cin >> n >> L >> R;

    for (uint i(1U); i <= n; ++i)
        cin >> a[i];

    init();

    // print(Mylog2+1U,Mylog2+n+1U);
    for (uint i(1U); i <= n; ++i)
        ans[i] = NINF;

    solve(1U, n);
    print(ans + 1U, ans + n + 1U);
    return 0;
}

写在后面

用你们发财的小手给主播点点赞和关注
我终于有精力开始维护这个博客了。

posted on 2024-03-28 15:26  AutiFancers  阅读(73)  评论(1编辑  收藏  举报