Codeforces Round #852 (Div. 2) 赛后做题

Codeforces Round #852 (Div. 2)

B. Fedya and Array

solution:

观察样例,好像数的构造没什么规律,但答案均为 $2(x-y)$

定义峰指比环上的两边大的数,谷指比环上的两边小的数

思考要让数的个数尽量少,那单调下降/上升的一段无贡献,且每个产生贡献的峰/谷的数值尽量大

我们不妨让第一个从峰 $x$ 开始,先下降到谷 $y$,再上升到 $x-1$,刚好满足了环的要求,$n=2(x-y)$

为什么最优呢?

假设最终序列从峰开始,峰谷交替,依次为 $x_i,y_i$,有 $k$ 对

因为每当从任意一个峰到达一个谷,至少需要 $x_i-y_i$ 个数(不包含最后的谷),同理从谷到下一个峰要 $x_{i+1}-y_i$ 个数,由于在环上,满足差的绝对值等于一的情况下首尾还至少要 $x_1-y_k$ 个数让峰谷连接

而 $\sum_{i=1}^k x_i=x,\sum_{i=1}^k y_i=y$,把上述相加,就是 $2(x-y)$

code:

int t, n, mx, mn, a[200010];
int main()
{
    t = read();
    while(t--)
    {
        mx = read(), mn = read(), n = 0;
        for(reg int i = mx; i >= mn; --i)   a[++n] = i;
        for(reg int i = mn + 1; i < mx; ++i)    a[++n] = i;
        print(n), putchar('\n');
        for(reg int i = 1; i <= n; ++i) print(a[i]), putchar(' ');
        putchar('\n');  
    }
    return 0;
} 

C. Dora and Search

solution:

暴力找肯定不行,但先无脑的想,如果开头结尾没有 $1$ 且没有 $n$,直接输出 $(1,n)$ 即可

那如果有呢?序列最大/小值一定在开头/结尾,不选它剩下数的最值一定不是它,不影响剩下数的答案,递归地思考不选它,把开头或结尾向里挪一格

问题变为了一个规模为 $n-1$ 的子问题

这样下去,如果左端点与右端点相交还没找到答案则无解,若有解直接输出

code:

int main()
{
    t = read();
    while(t--)
    {
        n = read(), st = mn = 1, ed = mx = n, flag = 0;
        for(reg int i = 1; i <= n; ++i) a[i] = read();
        while(st < ed)
        {
            if(a[st] != mn && a[st] != mx && a[ed] != mn && a[ed] != mx)
            {
                print(st), putchar(' '), print(ed), putchar('\n'), flag = 1;
                break;
            }
            if(a[st] == mn) ++st, ++mn;
            else if(a[st] == mx)    ++st, --mx;
            if(a[ed] == mn) --ed, ++mn;
            else if(a[ed] == mx)    --ed, --mx;
        }
        if(!flag)   putchar('-'), putchar('1'), putchar('\n'); 
    } 
    return 0;
}

D. Moscow Gorillas

solution:

$MEX([l,r])$ 真的很不好维护,何况还有两个排列

但从简单的问题想起,如果 $MEX([l,r])=1$,则 $[l,r]$ 一定不包含 $1$,可以直接算出方案数!

那么,枚举 $MEX$ 的值为 $x$,设 $p_{idp}=x,q_{idq}=x$,则 $[l,r]$ 中一定包含 $1\sim x-1$ 且不包含 $x$

可以在从小到大枚举时维护恰好包含 $1\sim x-1$ 的区间,记它为 $[l,r]$

分类讨论端点的大小:

  • $l\le idp,idq\le r$,则不可能有解

  • $idp,idq\le l$,$ans=(l-\max\{idp,idq\})\cdot(n-r+1)$

  • $idp,idq\ge r$,$ans=(\min\{idp,idq\}-r)\cdot l$

  • else, $ans=(l-min\{idp,idq\})\cdot (r-\max\{idp,idq\})$

每次更新 $l,r$,记得初始化 $x=1$ 时和加上全选的情况

code:

inline ll c2(ll x)  {return x * (x + 1) / 2;}
int main()
{
    n = read();
    for(reg ll i = 1; i <= n; ++i)  p[i] = read(), idp[p[i]] = i;
    for(reg ll i = 1; i <= n; ++i)  q[i] = read(), idq[q[i]] = i;
    l = min(idp[1], idq[1]), r = max(idp[1], idq[1]), ans += c2(l - 1) + c2(n - r) + c2(r - l - 1);
    for(reg ll i = 2; i <= n; ++i)
    {
        lasl = l, lasr = r;
        if((idp[i] >= l && idp[i] <= r) || (idq[i] >= l && idq[i] <= r))
        {
            l = min({l, idp[i], idq[i]}), r = max({r, idp[i], idq[i]});
            continue;
        }
        l = min({l, idp[i], idq[i]}), r = max({r, idp[i], idq[i]});
        if(max(idp[i], idq[i]) < lasl)  ans += (lasl - max(idp[i], idq[i])) * (n - lasr + 1);
        else if(min(idp[i], idq[i]) > lasr) ans += lasl * (min(idp[i], idq[i]) - lasr);
        else    ans += (lasl - l) * (r - lasr);     
    }
    printf("%lld", ans + 1);
    return 0;
}

E. Velepin and Marketing

solution:

dp好题,但不擅长猜结论的我推不出推论啊……

首先将 $a_i$ 从小到大排序

那让尽可能多的人满足

引理1:满足的人一定是序列的一个前缀

还比较好证,如果不是一个前缀,则换成前面 $a_i$ 更小的人一定还满足,答案不劣

但如果对于每本书,阅读它的人是两段不交的区间,就不好处理,所以神奇的地方在这里:

引理2:阅读每本书的人一定是连续的一段区间

受评论区大佬的启发,这里考虑交换证明

先设有两段不交区间 $A,B$,那想让 $A$ 中元素与在 $A$ 右边元素交换,实现 $A$ 右移,注意每段区间长度不变

看 $A$,右边元素 $a$ 更大都能满足,$A$ 中交换过去的一定满足

看右边元素,$B$ 中元素 $a$ 比它大都能满足,右边交换到 $A$ 的一定能满足

所以,答案不劣,$A$ 可以一直到与 $B$ 相邻位置,多个区间同理

根据引理 1,巧妙转化定 $k$ 求最大前缀长度为定前缀长度求最大 $k$

我们想到设 $f_i$ 表示处理了 $i$ 前元素后,前 $i$ 个元素最多被分成的集合数

$sum_i$ 为恰好划分为 $i$ 个集合时满足的最大人数

转移:$f_i=\max_{j\le i-a_i} f_j+1,sum_i=\max\{n-i+f_i\}$,用前缀最大值优化

但若 $a_i>i$,此时不可能满足,$f_i=-\infty,sum_i=\max\{n-a_i+1\}$

$sum_i$ 求后缀最大值,得到至少划分为 $i$ 个集合时满足的最大人数,直接输出 $sum_{k_i}$

code:

int main()
{
    n = read(); 
    for(reg int i = 1; i <= n; ++i) a[i] = read();
    sort(a + 1, a + n + 1);
    for(reg int i = 1; i <= n; ++i)
    {
        if(a[i] > i)    // 不可能满足,最多 a[i] 个人一个集合,剩下一人一个集合
            f[i] = -inf, sum[n - a[i] + 1] = max(sum[n - a[i] + 1], i);  
        else    // 其实是这段连续段的长度 >= a[i],找最大的 f[j],用上前缀最大值 
            f[i] = f[i - a[i]] + 1, sum[n - i + f[i]] = max(sum[n - i + f[i]], i);
        f[i] = max(f[i], f[i - 1]); // 前缀最大值优化
    }
    for(reg int i = n - 1; i >= 0; --i) sum[i] = max(sum[i + 1], sum[i]);
    q = read();
    for(reg int i = 1; i <= q; ++i)
    {
        k = read();
        print(sum[k]), putchar('\n');
    }
    return 0;
} 
posted @ 2023-02-18 23:54  KellyWLJ  阅读(4)  评论(0编辑  收藏  举报  来源