Codeforces Round #659 (Div. 2) (A、B1、B2、C 题题解)

B1 与 B2 思路不同

A. Common Prefixes

链接 :
A. Common Prefixes

题意 :
(多组输入)
给定长度为 n 的数组 a,输出 n + 1 个字符串 s.
sisi+1最长公共前缀长度ai.

数据范围 : n ⊂ [ 1 , 100 ] n \subset[1,100] n[1,100] a i ⊂ [ 0 , 50 ] a_i \subset[0,50] ai[0,50]

思路 :
注意到题目中 ai 范围[0,50],si 长度范围[1,200],说明可以先将 s0 直接初始为50个字符a.
然后每次遍历50个字符,若当前位置pos < ai-1,则 s[i][pos] = s[i - 1][pos],反之则不同.

AC代码 :

#include <iostream>
#include <cstdio>
#include <map>
#include <math.h>
#include <stack>
#include <queue>
#include <cstring>
#include <algorithm>
 
#define ll long long
#define pb push_back
using namespace std;
const int MAX = 1e2 + 10;
int a[MAX];
string s[MAX];
int main()
{
    int t, n, m;
    cin >> t;
    while (t--)
    {
        memset(s, 0, sizeof(s));
        cin >> n;
        for (int i = 0; i < n; i++)
            cin >> a[i];
        //这里我们只用字符b 与 字符c 因为这两个字符可以通过异或运算转换
        for (int i = 0; i < 50; i++)
            s[0] += 'b';
        for (int i = 1; i <= n; i++)
        {
            for (int j = 0; j < 50; j++)
            {
                if (j < a[i - 1])
                    s[i] += s[i - 1][j];
                else
                    s[i] += (s[i - 1][j] ^ 1);  //b与c的转换
            }
        }
        for (int i = 0; i <= n; i++)
            cout << s[i] << endl;
    }
 
    return 0;
}

B1. Koa and the Beach——Easy Version

链接 :
B1. Koa and the Beach (Easy Version)

题意 :
(多组输入)
一片海,分为 n 个海域,每个海域初始深度为 di.
2k 秒内这片海的 每个海域 都会有以下变化 :
k 秒,每秒海域深度都会增加1;
k 秒,每秒海域深度都会减少1;
Koa 站在岸边(不会深度变化的 0 处),想游过这片海到达对岸(不会深度变化的 n+1 处).
Koa每秒都可以选择 往前游一个海域 或者 停在当前海域,无论什么决定时间都会增加1,保证在过程中Koa处在不超过深度 l 的海域.
(游泳过程中深度不会变化)
问 : 是否可以游到对岸?

数据范围 : n ⊂ [ 1 , 100 ] n \subset[1,100] n[1,100] k ⊂ [ 1 , 100 ] k \subset[1,100] k[1,100] l ⊂ [ 1 , 100 ] l \subset[1,100] l[1,100] d i ⊂ [ 0 , 100 ] d_i \subset[0,100] di[0,100]

思路 :
使用dp,dp[i][j]表示 第 i 个区域的第 j 秒 是否可以游到 (值为1为可行)
Tip : 这里 j 的上限可以为 2k,但 应该 要加入时间的取模,AC代码中直接开到n * 2k.

初始化 0 处,均为可行点.
状态 可以由 当前海域上一秒的状态 和 上一个海域上一秒的状态 转移而来
即 dp[i][j] = max(dp[i][j - 1] , dp[i - 1][j - 1])
最后只需判断第 n 个位置是否有可行状态

AC代码 :

#include <iostream>
#include <cstdio>
#include <map>
#include <math.h>
#include <stack>
#include <queue>
#include <cstring>
#include <algorithm>
 
#define ll long long
#define pb push_back
using namespace std;
const int MAX = 100 + 10;
int dp[MAX][20010], d[MAX];
int main()
{
    int t, n, k, l;
    cin >> t;
 
    while (t--)
    {
        cin >> n >> k >> l;
        memset(dp, 0, sizeof(dp));
        for (int i = 1; i <= n; i++)
            cin >> d[i];
        
        //0 位置所有时间均合法
        for (int j = 0; j <= 20000; j++)
            dp[0][j] = 1;
        int pre = 0;
        for (int i = 1; i <= n; i++)
        {
            for (int j = 1; j <= 20000; j++)
            {
                int h;
                //h 表示当前时间下 海域i 的高度
                if (j % (2 * k) < k)
                    h = d[i] + (j % k);
                else
                    h = d[i] + k - (j % k);
                
                //判断高度是否合法
                if (h <= l)
                    dp[i][j] = max(dp[i - 1][j - 1], dp[i][j - 1]);
            }
        }
        int fg = 0;
        for (int j = 0; j <= 20000; j++)
        {
        	//判断 n 位置是否存在合法时间
            if (dp[n][j])
            {
                fg = 1;
                break;
            }
        }
        if (fg)
            cout << "Yes" << endl;
        else
            cout << "No" << endl;
    }
 
    return 0;
}

B2. Koa and the Beach——Hard Version

链接 :
B2. Koa and the Beach (Hard Version)

题意 :
B1

数据范围 : n ⊂ [ 1 , 3 ∗ 1 0 5 ] n \subset[1,3 * 10^5] n[1,3105] k ⊂ [ 1 , 1 0 9 ] k \subset[1,10^9] k[1,109] l ⊂ [ 1 , 1 0 9 ] l \subset[1,10^9] l[1,109] d i ⊂ [ 0 , 1 0 9 ] d_i \subset[0,10^9] di[0,109]

思路 :

DP在这里时间空间复杂度都会爆 (就算优化,只开一维 1e9也不行)

由DP可以想到
(以下的 时间区间 均只考虑 [0,2k)范围)
每个海域都可以从 上一个海域完全合法时间区间 与 当前海域符合深度的合法时间 取交集 得到当前的完全合法时间区间.
这样只要判断最后得到的完全合法时间区间是否有元素即可.

但这里 上个海域 到 当前海域 存在时间的变化,再加上每段[0,k)与[k,2k)深度变化不同,因此我们需要将每个海域的合法区间分开.
因此要对 两个 上个完全合法区间 与 两个 当前深度合法区间 取交集
(还需要特殊考虑两个深度合法区间相通的情况 即任意时间下深度合法)

AC代码 :

#include <iostream>
#include <cstdio>
#include <map>
#include <math.h>
#include <stack>
#include <queue>
#include <cstring>
#include <algorithm>
 
#define ll long long
#define pb push_back
using namespace std;
const int MAX = 100 + 10;
int main()
{
    int t, n, k, l;
    cin >> t;
 
    while (t--)
    {
        int fg = 0;
        cin >> n >> k >> l;
        int lt1 = 0, rt1 = k, rt2 = k;
        /*
        lt1 表示[0,k)合法时间左边界
        rt1 表示[0,k)合法时间右边界
        lt2 不需要 因为时间是向后推移 lt2不需要改变 视为k(若改变 状态已经为不合法)
        rt2 表示[k,2k)合法时间右边界
        */
        for (int i = 0; i < n; i++)
        {
            int d, t;
            scanf("%d", &d);
            //t 为最大的深度合法上限区间
            t = min(k, l - d);
            //t == k 代表在这个海域深度合法区间连通 可以等待任意的时间 (不相同则不能等待到下半区间)
            if (t == k)
            {
                lt1 = 0;
                rt1 = k;
                rt2 = k;
            }
            else
            {
                //若rt2超过2k范围 则lt1的位置就不能由2k-1转移 只能下移
                if (rt2 >= 2 * k)
                    lt1++;
                //取交集
                rt1 = min(t, rt1 + 1);
                rt2 = max(2 * k - t, rt2 + 1);
            }
            if (d > l || lt1 > rt1)
                fg = 1;
        }
        if (fg)
            cout << "No" << endl;
        else
            cout << "Yes" << endl;
    }
 
    return 0;
}

C. String Transformation 1

链接 :
C. String Transformation 1

题意 :
给定两个字符串s1 s2,你可以选 s1 中任意个相同字符都变成另一个大于变化前字符的字符
问最少操作数

思路 :
很容易想到,要尽可能将相同字符变到相同的目标字符打标记,求这样有多少组.
但考虑 aabb 到 bccc 只需要将2个a都变成b,再将后3个b变成c这样只需两步.

按照我们上面的思路是有a->b、a->c、b->c三次操作.
很明显能观察到这里是路径重复要去掉重复路径
并且我们这里只能从小字符大字符变化,就可以用并查集合并路径
对 (每个连通块里的元素个数-1) 求和即可

AC代码 :

#include <iostream>
#include <cstdio>
#include <map>
#include <math.h>
#include <stack>
#include <queue>
#include <cstring>
#include <algorithm>
 
#define ll long long
#define pb push_back
using namespace std;
const int MAX = 100 + 10;
int sum[MAX], fa[MAX];
char s1[100010], s2[100010];
int find(int x)
{
    if (fa[x] != x)
        fa[x] = find(fa[x]);
    return fa[x];
}
void combine(int x, int y)
{
    int temp1 = find(x);
    int temp2 = find(y);
    if (temp1 != temp2)
    {
        sum[temp1] += sum[temp2];
        fa[temp2] = temp1;
    }
}
int main()
{
    int t, n;
    cin >> t;
 
    while (t--)
    {
        int fg = 0;
        memset(fa, 0, sizeof(fa));
        memset(sum, 0, sizeof(sum));
        cin >> n >> s1 >> s2;
        for (int i = 1; i <= 20; i++)
        {
            fa[i] = i;
            sum[i] = 1;
        }
        for (int i = 0; i < n; i++)
        {
        	//若不符合直接跳出
            if (s1[i] > s2[i])
            {
                fg = 1;
                break;
            }
            //合并(不可能有环)
            combine(s1[i] - 'a' + 1, s2[i] - 'a' + 1);
        }
        if (fg)
            cout << -1 << endl;
        else
        {
            int cnt = 0;
            for (int i = 1; i <= 20; i++)
            {
                if (find(i) == i)
                {
                    cnt += sum[i] - 1;
                }
            }
            cout << cnt << endl;
        }
    }
 
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值