Codeforces Round #637 个人题解

Codeforces Round #637 个人题解

1341A - Nastya and Rice(交集判断)

题意:Nastya 在地上捡起 n ​ n​ n 颗米粒,已知每颗米粒的重量在 [ a − b , a + b ] ​ [a-b, a+b]​ [ab,a+b] 区间中,并且捡起的这 n ​ n​ n 颗米粒实际总重量在 [ c − d , c + d ] ​ [c-d, c+d]​ [cd,c+d] 区间中,现在问是否存在一种米粒的重量分布使得总重量在 [ c − d , c + d ] ​ [c-d, c+d]​ [cd,c+d] 区间中,是则输出 Y e s ​ Yes​ Yes,否则输出 N o ​ No​ No,需要回答 t ​ t​ t 个案例。

范围 1 ≤ t ≤ 1000 , 1 ≤ n ≤ 1000 , 0 ≤ b < a ≤ 1000 , 0 ≤ d < c ≤ 1000 ​ 1 \le t \le 1000,1 \le n \le 1000, 0 \le b < a \le 1000, 0 \le d < c \le 1000​ 1t1000,1n1000,0b<a1000,0d<c1000

题解

考虑极限情况下着 n ​ n​ n 颗米粒的总重量会在区间 [ n ( a − b ) , n ( a + b ) ] ​ [n(a-b), n(a+b)]​ [n(ab),n(a+b)] 中浮动,因此只需要判断该区间和 [ c − d , c + d ] ​ [c-d, c+d]​ [cd,c+d] 这两个区间是否有交集。

Code

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

// const int MAXN = 2e5 + 10;
const int INF = 0x3f3f3f3f;
const int MOD = 1e9 + 7;
const double eps = 1e-9;
const double PI = acos(-1.0);

int n;

inline int read()
{
    int s = 0, w = 1;
    char ch = getchar();
    while (ch < '0' || ch > '9')
    {
        if (ch == '-')
            w = -1;
        ch = getchar();
    }
    while (ch >= '0' && ch <= '9')
        s = s * 10 + ch - '0', ch = getchar();
    return s * w;
}

signed main()
{
    int T = read();
    while (T--)
    {
        n = read();
        int a, b, c, d;
        cin >> a >> b >> c >> d;
        if ((a - b) * n > (c + d) || (a + b) * n < (c - d))
            cout << "No" << endl;
        else
            cout << "Yes" << endl;
    }
    return 0;
}

1341B - Nastya and Door(预处理+窗口区间和)

题意:有一个长度为 n ​ n​ n 的数字序列 a ​ a​ a,其中有若干个 p e e k ​ peek​ peek p e e k ​ peek​ peek定义为比左右相邻数字来得大的数字(需要保证左右两边都有数字),现在问该数据序列中长度为 k ​ k​ k 的子区间最多会有多少个 p e e k ​ peek​ peek,需要回答 t ​ t​ t 个案例。

范围 1 ≤ t ≤ 1 e 4 , 3 ≤ k ≤ n ≤ 2 e 5 , 0 ≤ a i ≤ 1 e 9 1 \le t \le 1e4, 3 \le k \le n \le 2e5, 0\le a_i\le 1e9 1t1e4,3kn2e5,0ai1e9

保证所有 c a s e case case n n n 的总和不超过 2 e 5 2e5 2e5

题解

首先我们可以先预处理出一个新的序列 b ​ b​ b

b [ i ] = { 1 ,   a [ i ]   i s   a   p e e k 0 ,   a [ i ]   i s n ′ t   a   p e e k b[i] = \begin{cases}1,~a[i]~is~a~peek \\ 0,~a[i]~isn't ~a~peek\end{cases} b[i]={1, a[i] is a peek0, a[i] isnt a peek

那么问题就转换成为对于这样一个 01 01 01 串,我们要求所有长度为 k k k 的子区间中最大的区间和。

因此我们可以维护一个长度为 k k k 的窗口,从左到右遍历所有长度为 k ​ k​ k 的区间进行统计区间和并且更新答案即可。

详见代码。

Code

#include <bits/stdc++.h>
#define int long long
#define double long double
using namespace std;

const int MAXN = 2e5 + 10;
const int INF = 0x3f3f3f3f;
const int MOD = 1e9 + 7;
const double eps = 1e-9;
const double PI = acos(-1.0);

int n;

inline int read()
{
    int s = 0, w = 1;
    char ch = getchar();
    while (ch < '0' || ch > '9')
    {
        if (ch == '-')
            w = -1;
        ch = getchar();
    }
    while (ch >= '0' && ch <= '9')
        s = s * 10 + ch - '0', ch = getchar();
    return s * w;
}

int arr[MAXN], brr[MAXN];

signed main()
{
    int T = read();
    while (T--)
    {
        n = read();
        int k = read();
        k -= 2;  // 边界的两个值不需要考虑,且k>=3,因此可以先减去2
        for (int i = 0; i < n; i++)
        {
            arr[i] = read();
        }
        // 预处理出b数组
        for (int i = 0; i < n; i++)
        {
            if (i == 0 || i == n - 1)
                brr[i] = 0;
            else
            {
                if (arr[i] > arr[i - 1] && arr[i] > arr[i + 1])
                {
                    brr[i] = 1;
                }
                else
                    brr[i] = 0;
            }
        }
        // 先维护一个长度为k的窗口
        int sum = 0;
        for (int i = 0; i < k; i++)
        {
            sum += brr[i];
        }
        // 再从左往右遍历所有长度为k的区间,保证答案字典序最小
        int ans = sum, idx = 1;
        for (int i = k; i < n; i++)
        {
            sum = sum - brr[i - k] + brr[i];  // 每次减去首部的值,加上新加入的值
            if (sum > ans)  // 更新答案
            {
                ans = sum;
                idx = i - k + 1;
            }
        }
        cout << ans + 1 << " " << idx << endl;
    }
    return 0;
}

1341C - Nastya and Strange Generator(找规律+模拟判断)

题意:(欢迎来到阅读理解题)有一个长度为 n n n 的排列 p p p,一开始所有位置都没有被占据。定义 r i r_i ri 为距离位置 i i i 最近的没有被占据的位置, c o u n t i count_i counti 为所有 r r r r j = = i r_j == i rj==i j j j 数量。规矩规定如果想要把数字加入到 p p p 中,必须选择 c o u n t count count 最大的位置,如果有多个位置满足条件,那么可以任意选择其中一个。现在给了被占满的排列 p p p,问其能不能通过合法的方式从空排列形成,需要回答 t ​ t​ t 个案例。

范围 1 ≤ t ≤ 1 e 5 , 1 ≤ n ≤ 1 e 5 , 1 ≤ p i ≤ n 1 \le t \le 1e5, 1 \le n \le 1e5, 1 \le p_i \le n 1t1e5,1n1e5,1pin

保证所有 c a s e case case n n n 的总和不超过 1 e 5 1e5 1e5

题解

先看个例子,一个长度为 7 7 7 的空排列,一开始 r i = i , c o u n t i = 1 r_i = i, count_i = 1 ri=i,counti=1

在这里插入图片描述

这个时候我们可以随便选,假设我们选择了 5 5 5,那么 r r r 数组变成这样
在这里插入图片描述

此时 c o u n t 6 count_6 count6 出现的次数最多,所以必须选择 6 6 6

在这里插入图片描述

这样以后 c o u n t 7 count_7 count7 出现的次数最多,必须选择 7 7 7

可以发现一旦我们选择了从一个位置开始,那么接下来必定只能选择其右侧相邻的数字,直至处理到边界或者碰到了被占据的位置。

所以最后呈现出来的排列必定是由若干个紧密递增的子序列拼接而成的,因此我们只要用这种逻辑去检查给定的 p ​ p​ p 数组即可。

详见代码。

Code

#include <bits/stdc++.h>
#define int long long
#define double long double
using namespace std;

const int MAXN = 1e5 + 10;
const int INF = 0x3f3f3f3f;
const int MOD = 1e9 + 7;
const double eps = 1e-9;
const double PI = acos(-1.0);

int n;

inline int read()
{
    int s = 0, w = 1;
    char ch = getchar();
    while (ch < '0' || ch > '9')
    {
        if (ch == '-')
            w = -1;
        ch = getchar();
    }
    while (ch >= '0' && ch <= '9')
        s = s * 10 + ch - '0', ch = getchar();
    return s * w;
}

int arr[MAXN], vis[MAXN];  // vis[i]标记位置i是否被标记

map<int, int> mp;  //  保存每个数字的位置,用数组也可以

signed main()
{
    int T = read();
    while (T--)
    {
        mp.clear();
        n = read();
        for (int i = 0; i <= n; i++)
            vis[i] = 0;
        for (int i = 0; i < n; i++)
        {
            arr[i] = read();
            mp[arr[i]] = i;
        }
        int now;  // now保存当前处理到的数字
        int cnt = 0;  // cnt表示已经处理了多少个数字
        int flag = 1;  // flag表示是否出现违法的情况
        while (cnt < n)  // 一共需要填n个数字
        {
            cnt++;
            // 确定递增子序列的起点
            now = mp[cnt];
            vis[now] = 1;
            // 从起点开始一直往右处理,直到遇见边界或者遇见已经被占据的点
            for (int i = now + 1; i < n; i++)
            {
                if (vis[i])
                    break;
                vis[i] = 1;
                cnt++;
                // 如果不是紧密递增的话,说明出现了违法情况
                if (arr[i] != arr[i - 1] + 1)
                {
                    flag = 0;
                    break;
                }
            }
            if (!flag)
                break;
        }
        if (flag)
        {
            cout << "Yes" << endl;
        }
        else
        {
            cout << "No" << endl;
        }
    }
    return 0;
}

1341D - Nastya and Scoreboard(dfs与dp预处理+贪心)

题意:由 7 7 7 个二极管可以显示出 0 ∼ 9 0 \sim 9 09 个数字,现在有 n n n 个这样的二极管,其中已经有一些二极管已经是亮着。现在必须要再点亮 k k k 根二极管,输出能够显示的最大数字,要求每个数字都是显示正确的,可以有前导 0 0 0,若没有办法形成有效的数字序列,则输出 − 1 -1 1

范围 1 ≤ n ≤ 2000 , 0 ≤ k ≤ 2000 1 \le n \le 2000, 0 \le k \le 2000 1n2000,0k2000

每个单位的二极管亮暗通过以下顺序的 01 01 01 串描述
在这里插入图片描述

题解

首先我们需要解决的问题是将当前的二极管状态变成数字 0 ∼ 9 0\sim 9 09 需要点亮多少个二极管,或者根本不能进行转换。

考虑到字符串长度只有 7 7 7,这里我使用了 d f s dfs dfs 枚举所有长度为 7 7 7 的二极管状态并且计算每种状态转移到数字 0 ∼ 9 0\sim 9 09 所需要点亮的二极管数量。

解决了这个问题之后我们正式开始考虑这个问题本身,题目要我们求最大能得到的数字串,那么我们自然想要从左往右对每个数字尽量取较大的数字,但是我们不能确定当前位置选择了某个数字,花费了相应的代价之后,后面还能不能形成正确的数字串。那么这个就是本问题的核心问题了。

考虑到 n ​ n​ n 最大只会到 2000 ​ 2000​ 2000,因此这里我使用了二维 d p ​ dp​ dp 进行预处理, d p [ i ] [ j ] ​ dp[i][j]​ dp[i][j] 表示在位置 i ​ i​ i 必须点亮 j ​ j​ j 根二极管是否有解。

转移方程:

d p [ n ] [ 0 ] = 1 dp[n][0] = 1 dp[n][0]=1

d p [ i ] [ j ] = d p [ i ] [ j ]   ∣   d p [ i + 1 ] [ j − c o s t ( i , k ) ]    ( 0 ≤ k ≤ 9 ) ​ dp[i][j] = dp[i][j]~|~dp[i+1][j-cost(i, k)]~~(0 \le k \le 9)​ dp[i][j]=dp[i][j]  dp[i+1][jcost(i,k)]  (0k9)

其中 c o s t ( i , j ) cost(i, j) cost(i,j) 表示从二极管状态 i i i 转换成数字 k k k 需要点亮的二极管数量,前面已经通过 d f s dfs dfs 得到。

做完了前面的准备工作,那么接下来我们判断 d p [ 0 ] [ k ] dp[0][k] dp[0][k] 是否为 0 0 0 就可以知道是否存在有效解,如果存在有效解,则从左往右处理每个位置,每个位置的数字从大到小进行枚举,得到最大的数字进行输出。

详见代码。

Code

#include <bits/stdc++.h>
#define int long long
#define double long double
using namespace std;

const int MAXN = 2000 + 10;
const int INF = 0x3f3f3f3f;
const int MOD = 1e9 + 7;
const double eps = 1e-9;
const double PI = acos(-1.0);

int n;

inline int read()
{
    int s = 0, w = 1;
    char ch = getchar();
    while (ch < '0' || ch > '9')
    {
        if (ch == '-')
            w = -1;
        ch = getchar();
    }
    while (ch >= '0' && ch <= '9')
        s = s * 10 + ch - '0', ch = getchar();
    return s * w;
}

string arr[MAXN];

int dp[MAXN][MAXN];

map<pair<string, string>, int> mp;

map<int, string> itos;

void dfs(int cur, string str)
{
    if (cur == 7)
    {
        // 处理字符串str到数字0~9的代价
        for (int i = 0; i < 10; i++)
        {
            string temp = itos[i];
            int sum = 0;
            for (int j = 0; j < 7; j++)
            {
                if (str[j] == '0')
                {
                    // 从无到有是允许的
                    if (temp[j] == '1')
                    {
                        sum++;
                    }
                }
                else
                {
                    // 把已经点亮的灯熄灭是不允许的
                    if (temp[j] == '0')
                    {
                        mp[make_pair(str, temp)] = -1;
                        break;
                    }
                }
                if (j == 6)
                {
                    mp[make_pair(str, temp)] = sum;
                }
            }
        }
        return;
    }
    str += '0';
    dfs(cur + 1, str);
    str[str.length() - 1] = '1';
    dfs(cur + 1, str);
}

signed main()
{
    // dfs预处理,得到所有长度为7的字符串转移到0~9数字所需要的代价
    itos[0] = "1110111";
    itos[1] = "0010010";
    itos[2] = "1011101";
    itos[3] = "1011011";
    itos[4] = "0111010";
    itos[5] = "1101011";
    itos[6] = "1101111";
    itos[7] = "1010010";
    itos[8] = "1111111";
    itos[9] = "1111011";
    dfs(0, "");
    n = read();
    int k = read();
    for (int i = 0; i < n; i++)
    {
        cin >> arr[i];
    }
    dp[n][0] = 1;  // 这里需要注意,方便后面操作的统一
    for (int i = n - 1; i >= 0; i--)
    {
        // 先将当前二极管单位可以变成的数字的代价加入vector
        vector<int> vec;
        for (int t = 0; t < 10; t++)
        {
            string temp = itos[t];
            int need = mp[make_pair(arr[i], temp)];  // need就是代价
            if (need == -1)
                continue;
            vec.push_back(need);
        }
        for (int j = 0; j <= k; j++)
        {
            // 枚举所有可以变成的数字,更新dp数组
            for (auto need : vec)
            {
                if (j >= need && dp[i + 1][j - need])
                {
                    dp[i][j] = 1;
                }
            }
        }
    }
    if (dp[0][k] == 0)  // 无解
    {
        cout << -1 << endl;
    }
    else
    {
        // 从左往右 从上到下进行贪心的选择
        int now = k;
        for (int i = 0; i < n; i++)
        {
            for (int j = 9; j >= 0; j--)
            {
                string temp = itos[j];
                int need = mp[make_pair(arr[i], temp)];
                if (need == -1)
                    continue;
                if (now >= need && dp[i + 1][now - need])  // 可以选择,并且后面有办法形成有效数组
                {
                    cout << j;
                    now -= need;
                    break;
                }
            }
        }
        cout << endl;
    }
}

【END】感谢观看!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值