首页 > 其他分享 >[PA2021] Wystawa

[PA2021] Wystawa

时间:2023-12-22 20:11:07浏览次数:37  
标签:cont val leftarrow int 位置 st Wystawa PA2021

[PA2021] Wystawa

牛逼啊喔趣。

题意

给定长度为 \(n\) 的序列 \(a, b\)。

你需要构造一个序列 \(c\),构造方法为:

  • 选择 \(k\) 个 \(i\),令 \(c_i \leftarrow a_i\)。
  • 对于其他 \(i\),令 \(c_i \leftarrow b_i\)。

求序列 \(c\) 的最大子段和的最小值,并给出一种方案。

Sol

感觉最小值很难直接做,我们可以考虑判断是否存在方案满足最大子段和 \(\le x\)。

然后会发现如果 \(a_i < b_i\) 那么这时选 \(i\) 一定不劣。假如有 \(K\) 个位置满足这样的条件。

那么首先钦定 \(K \ge k\),否则可以交换原本的 \(a,b\) 序列,同时 \(k \leftarrow n - k,K\leftarrow n-K\)。

所以我们可以设想先选了 \(K\) 个位置,使得这时的最大子段和比所有方案都还要小。所以现在要解决的就是我们需要把 \(K-k\) 个位置从原本选择的 \(a_i\) 换成 \(b_i\),使得最大子段和最小。

策略应是,尽量少的损失下使被换成 \(b_i\) 的 \(a_i\) 个数到达 \(K-k\) 个。

所以如果加上这个位置后维护的 \(c\) 的子段和 \(s\) 变成负数,可以发现其实应该尽量多地压榨这个出现的负数(因为在指针跳到下一个位置前为了保持最大会使 \(s \leftarrow 0\))。

当然,如果 \(s>x\) 那么直接返回 \(F\)。

所以考虑尽量多地将前面的一些 \(a_i\) 变成 \(b_i\)。这样一定不会更劣。所以我们考虑维护一个数据结构保存 \(a_i\) 的贡献。贡献为 \(b_i-a_i\)。那么实际上我们就是将 \(b_i-a_i\) 最小的数通过这个负数给消掉。而每次我们让一个原本 \(c_i=a_i\) 的位置 \(c_i\leftarrow b_i\),会使得 \(s\leftarrow s+val\) 变大,同时我们成功地删去了 \(K-k\) 中的一个位置。如果 \(s\) 在下一个位置发生变化时则不能继续压榨了,结束这次压榨,同时原本下一个准备压榨 \(s\) 的贡献位置 \(i\) 的贡献应该增加 \(s\),因为这个位置下一次被变为 \(b_i\) 时贡献必须要经过这个 \(s\)。就会被减掉一些值。因为 \(s<0\) 所以不用担心下次拿出的贡献少考虑什么东西,因为如果要拿当前可以使用的贡献中的其他一部分就必须先使用这个位置 \(i\)。而加上这个 \(i\) 的贡献之后这段又会变成非负数。所以归纳一下,正确性是有的。

但是因为 \(c_i\leftarrow b_i\) 导致了最大子段和 \(>x\) 的情况上述的策略是没有考虑到的。所以我们可以考虑更进一步,考虑再记录一个 \(s_b\) 表示能选 \(b\) 时尽量选 \(b\) 的最大子段和。如果某个时刻 \(s_b > x\),那么考虑就要将前边的一些可以选 \(a_i\) 或 \(b_i\) 的不定位置强制选择 \(a_i\)。

仍然考虑尽量压榨,尽量少地选择强制为 \(a_i\) 的位置。所以我们应该选贡献尽量大的位置。考虑仍然在上述同一个数据结构中选择最大贡献的位置并使其强制选择 \(a_i\)(实际上相当于将 \(i\) 直接从数据结构中删去而不计入在删去的 \(K-k\) 个数中)。如果 \(s_b\le x\),那么停止选择。

当然最终不能忘记将还没有选择的一些位置。这时仍然先选小的贡献,意义是删去 \(K-k\) 中的一个位置,同时让 \(s\) 加上贡献 \(val\)。

最后的判断条件是 \(K-k\) 个位置全部完成替换且 \(s\le x\)。可以发现不会多替换了。如果存在某个子段的 \(s>x\),则应当已经提前退出了。

Code

跟 Luogu 上的一篇题解很像,因为我是照着看代码才看懂的,所以实现也很类似。

#include <bits/stdc++.h>
using namespace std;
#define int long long
struct cont {
    int val, pos;
    cont (int VAL = 0, int POS = 0){
        val = VAL;
        pos = POS;
    }
    bool operator < (const cont &oth) const {
        if (val == oth.val)
            return pos < oth.pos;
        return val < oth.val;
    }
};
int n, k, K, a[100005], b[100005];
char ans[100005];
set < cont > st;
bool is_swap;
bool Cmp (int A, int B){
    if (is_swap)
        return A <= B;
    return A < B;
}
bool Check (int lim){
    int s = 0, sb = 0, rest = K - k;
    st.clear ();
    for (int i = 1;i <= n;++ i){
        if (Cmp (a[i], b[i])){
            s += a[i];
            ans[i] = 'A';
            st.insert (cont (b[i] - a[i], i));
        } else {
            s += b[i];
            ans[i] = 'B';
        }
        if (s > lim)
            return false;
        int v, p;
        while (rest > 0 && s < 0 && ! st.empty ()){
            set < cont > :: iterator it = st.begin ();
            v = (*it).val, p = (*it).pos;
            if (s + v <= 0){
                -- rest;
                s += v;
                ans[p] = 'B';
                st.erase (it);
            } else {
                v += s;
                st.erase (it);
                st.insert (cont (v, p));
                break;
            }
        }
        s = max (s, 0ll);
        sb = max (sb + b[i], 0ll);
        while (sb > lim && ! st.empty ()){
            set < cont > :: reverse_iterator it = st.rbegin ();
            v = (*it).val;
            p = (*it).pos;
            sb -= v;
            st.erase (cont (v, p));
        }
        if (sb > lim)
            return false;
    }
    while (rest > 0 && ! st.empty ()){
        set < cont > :: iterator it = st.begin ();
        -- rest;
        int v = (*it).val, p = (*it).pos;
        s += v;
        ans[p] = 'B';
        st.erase (it);
    }
    return rest == 0 && s <= lim;
}
signed main (){
    scanf ("%lld%lld", &n, &k);
    for (int i = 1;i <= n;++ i)
        scanf ("%lld", &a[i]);
    for (int i = 1;i <= n;++ i)
        scanf ("%lld", &b[i]);
    for (int i = 1;i <= n;++ i)
        if (a[i] < b[i])
            ++ K;
    if (k > K){
        k = n - k;
        K = n - K;
        is_swap = true;
        for (int i = 1;i <= n;++ i)
            swap (a[i], b[i]);
    }
    int L = 0, R = 0x3f3f3f3f3f3f3fll, mid;
    while (L < R){
        mid = (L + R) >> 1;
        if (Check (mid))
            R = mid;
        else
            L = mid + 1;
    }
    Check (L);
    printf ("%lld\n", L);
    for (int i = 1;i <= n;++ i)
        putchar (((ans[i] - 'A') ^ (int) is_swap) + 'A');
    return 0;
}

标签:cont,val,leftarrow,int,位置,st,Wystawa,PA2021
From: https://www.cnblogs.com/imcaigou/p/17922298.html

相关文章

  • P8386 [PA2021] Od deski do deski 题解
    显然是一道计数dp。dp状态应该是最难的一部分了,个人认为这种状态设计得比较巧妙。如果像我刚开始一样设\(dp_{i,j}\)表示序列中一共有\(i\)个数,序列最后一个数为\(j\)的合法方案数的话,那么方程就会变得很不好转移,因为我们不知道当前的\(j\)和之前的某些数能不能匹配上,......
  • [PA2021] Poborcy podatkowi
    令\(dp_{x,d}\)表示\(x\)子树内现在根结点上挂着的链的长度为\(d\)的最大收益,那么转移时只要考虑一个点的子节点如何进行合并,注意到只有\(1,3\)消,\(2,2\)消两种互消的\(\text{case}\),相当于转移相当于\(\text{fix}\)\(a-c=d_{1}(|d_{1}|\leqslant1)\)且\(b\)\(\te......
  • [PA2021] Od deski do deski
    [PA2021]Oddeskidodeski看似简单,实则考察的是选手的DP基本功,如果像我一样只会观察性质就做不出来这题。性质:合法的序列一定是由若干个子串按照顺序拼起来的,其中每个子串的开头和结尾是一样的。然后的想法就是设\(f_i\)表示子串\(i\)能一次消掉的方案数,然后就会一直算......
  • P8386 [PA2021] Od deski do deski
    P8386[PA2021]Oddeskidodeski洛谷:OddeskidodeskiLOJ3600OddeskidodeskiSolution先考虑如何判定一个序列\(a\)是否合法。记\(dp_{i}=0/1\)表示考虑前\(i\)个数是否能被删空:\(dp_{i}\xleftarrow{\texttt{or}}dp_{j-1}[a_i=a_j]\)。初始化\(dp_{0}=......
  • [PA2021] Od deski do deski (dp)
    计数数列个数,要满足能划分为若干个两端相等区间。首先容易想到DP。我想的是按段分阶段转移,显然不行,因为很容易算重,一个数列能有多种划分方案则会被算多次。因此直接计数数列的每位,\(g(i,j)\)表示前\(i\)位有\(j\)种值存在位置的前一位往前的数列为合法序列的合法序列方案数,\(f(......
  • 题解:[PA2021] Drzewo czerwono-czarne
    题目链接:[PA2021]Drzewoczerwono-czarne首先对于起始和终止相同以及起始中只有一种颜色并且终止和起始不相同这两种情况是平凡的。考虑最后一步,一定是将某一条边上的一......