CF1032C Playing Piano

CF1032C Playing Piano - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

题目大意是:能否构造一个长度为 \(n\) 的值域为 \([1, 5]\) 的整数序列 \(b\),使得相邻两个数之间的大小关系满足给定的大小关系。

给定的大小关系可能是大于、小于、不等于。

CF 官方给的 dp 做法是假的。实际上只需要一个橙色难度的简单构造。而且这个做法时空复杂度和 \(b\) 的值域完全无关。dp 就相对受限了。

把序列拆分为连续大于段,连续小于段和连续不等段。

贪心地,连续大于段从 \(5\) 开始,填 \((5, 4, 3, 2, 1)\)。连续小于段从 \(1\) 开始,填 \((1, 2, 3, 4, 5)\)。填不下去了就是连续长度大于 \(5\) 了,报个无解。

连续不等段直接在 \(2, 3\) 之间振荡。\(3, 4\) 之间,\(2, 4\) 之间也可以,但不要涉及到 \(1, 5\),看后面就知道原因了。

然后考虑一下段和段的边界。

先小于后大于 \(a < b < c > d > e\)。发现 \(c\) 明显应该填 \(5\),也就是跟随后面那个段,因为要让后面尽可能连续下去。按照上面的贪心填法对应的构造方案是 \((1, 2, 5, 4, 3)\)

先大于后小于同理,跟后面。

先不等后小于 \(a \ne b \ne c < d < e\)。会发现 \(c\) 应该填 \(1\) 最好,也是跟随后面。这里就是为什么不等段的振荡最好不涉及 \(1\)\(5\),否则可能影响边界。

先不等后大于同理,跟后面。

先小于后不等 \(a < b < c \ne d \ne e\),这个 \(c\) 应该跟前面。

先大于后不等同理,跟前面。

但是这样会有一个问题:\(\cdots > a > b > c \ne d < e < f < \cdots\)\(d\) 应该怎么填?如果 \(c\) 没填 \(1\) 自然老办法 \(d\)\(1\),如果 \(c\) 填了 \(1\) 呢?

不难发现,此时 \(d\) 只能填 \(2\)。因为根据我们的贪心想法,\(c\) 此时都为 \(1\) 了就说明前面必有一段 \(5 > 4 > 3 >2 >1\) 的链,将 \(c\) 修改为 \(2\) 前面就会不合法。大小于号翻过来后情况也是类似的。

注意 \(n = 1\) 可能需要特判,根据你咋写的。

复杂度 \(\Theta(n)\)

/*
 * @Author: crab-in-the-northeast 
 * @Date: 2023-01-07 01:36:54 
 * @Last Modified by: crab-in-the-northeast
 * @Last Modified time: 2023-01-07 02:23:00
 */
#include <bits/stdc++.h>
inline int read() {
    int x = 0;
    bool f = true;
    char ch = getchar();
    for (; !isdigit(ch); ch = getchar())
        if (ch == '-')
            f = false;
    for (; isdigit(ch); ch = getchar())
        x = (x << 1) + (x << 3) + ch - '0';
    return f ? x : (~(x - 1));
}

const int maxn = (int)1e5 + 5;

int a[maxn];
char b[maxn]; // b[i] 表示 b[i] 和 b[i + 1] 的大小关系

int main() {
    int n = read();
    if (n == 1) {
        puts("1");
        return 0;
    }
    
    for (int i = 1; i <= n; ++i)
        a[i] = read();
    for (int i = 1; i < n; ++i) {
        if (a[i] < a[i + 1])
            b[i] = '<';
        else if (a[i] > a[i + 1])
            b[i] = '>';
        else
            b[i] = '!';
    }
    
    int lst = 1;
    if (b[1] == '>')
        lst = 5;
    else if (b[1] == '!')
        lst = 2;
    
    std :: vector <int> ans = {lst};
    
    for (int i = 2; i < n; ++i) {
        int now;
        char p = b[i - 1], q = b[i];
        if (p == '<') {
            if (lst == 5)
                break;
            now = lst + 1;
            if (q == '>')
                now = 5;
        } else if (p == '>') {
            if (lst == 1)
                break;
            now = lst - 1;
            if (q == '<')
                now = 1;
        } else if (p == '!') {
            if (q == '!') {
                now = 3;
                if (lst == 3)
                    now = 2;
            } else if (q == '<') {
                now = 1;
                if (lst == 1)
                    now = 2;
            } else if (q == '>') {
                now = 5;
                if (lst == 5)
                    now = 4;
            }
        }

        ans.push_back(lst = now);
    }

    if (b[n - 1] == '<')
        ans.push_back(5);
    else if (b[n - 1] == '>')
        ans.push_back(1);
    else
        ans.push_back(lst == 5 ? 4 : 5);
    
    if ((int)ans.size() == n)
        for (int x : ans)
            printf("%d ", x);
    else
        printf("-1");
    puts("");
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值