Codeforces Round 972 (Div. 2) A~E

A. Simple Palindrome (贪心)

题意:

N a r e k Narek Narek需要制作一个仅由元音组成的字符串。制作完字符串后,他会要求孩子们计算回文子序列的数量。 N a r e k Narek Narek希望保持简单,所以他正在寻找一个字符串,使得回文子序列的数量最少。 帮助 N a r e k Narek Narek找到一个长度为 n n n的字符串,该字符串仅由小写英语元音组成(字母 a \mathtt{a} a e \mathtt{e} e i \mathtt{i} i o \mathtt{o} o u \mathtt{u} u),从而最小化字符串中的回文子序列数量。

分析:

我们以 a e i o u aeiou aeiou为一组,如果再考虑插入一个新字符,可以发现所有相同字符排在一起才是最优,例如 a a e i o u aaeiou aaeiou。而对于每个字符数量则以尽可能均分为最优。

代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
#define endl '\n'
#define PII pair<LL, LL>
const int N = 3e5 + 10;
const int InF = 2e9 + 5;
const int mod = 1e9 + 7;
string s = "aeiou";
int a[5];
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    int t;
    cin >> t;
    while (t--)
    {
        int n;
        cin >> n;
        string ans;
        memset(a, 0, sizeof(a));
        for (int i = 0; i < n % 5; i++)
        {
            a[i]++;
        }
        for (int i = 0; i < 5; i++)
        {
            a[i] += n / 5;
        }
        for (int i = 0; i < 5; i++)
        {
            for (int j = 0; j < a[i]; j++)
            {
                ans += s[i];
            }
        }
        cout << ans << endl;
    }
    return 0;
}

B2.The Strict Teacher (Hard Version) (二分)

题意:

现在有 m m m名老师一起追捕 D a v i d David David。幸运的是,教室很大,所以 D a v i d David David有很多地方可以躲藏。教室可以表示为一条线,单元格从 1 1 1 n n n。 开始时,所有 m m m名老师和 D a v i d David David都在不同的单元格中。然后他们开始行动。每次行动时

  • D a v i d David David会前往相邻的单元格或留在当前单元格。

  • 每位 m m m老师同时前往相邻的单元格或留在当前单元格。

这种情况会一直持续到 D a v i d David David被抓住。如果任何一位老师(可能不止一位)与 D a v i d David David位于同一个单元格, D a v i d David David就会被抓住。每个人都能看到其他人的动作,因此他们都采取了最佳行动。 你的任务是找出如果老师们都采取最佳行动,他们需要多少步才能抓住 D a v i d David David

分析:

分情况讨论,如果 D a v i d David David被老师夹在中间,假设两位老师之间有 k k k个空位,那么 D a v i d David David能活 ⌈ k 2 ⌉ \lceil \frac{k}{2} \rceil 2k轮。我们可以对 b b b排序后用 l o w e r b o u n d lowerbound lowerbound找到 D a v i d David David左右的老师。 如果 D a v i d David David没有被老师夹住:那么 D a i v d Daivd Daivd显然会贪心地往边界跑,那么只需要求出离他最近的老师 x x x的位置即可。

代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
#define endl '\n'
#define PII pair<LL, LL>
const int N = 3e5 + 10;
const int InF = 2e9 + 5;
const int mod = 1e9 + 7;
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    int t;
    cin >> t;
    while (t--)
    {
        int n, m, q;
        cin >> n >> m >> q;
        vector<int> b(m);
        for (int &x : b)
            cin >> x;
        sort(begin(b), end(b));
        while (q--)
        {
            int x;
            cin >> x;
            if (x < b[0])
            {
                cout << b[0] - 1 << endl;
            }
            else if (x > b.back())
            {
                cout << n - b.back() << endl;
            }
            else
            {
                int p = upper_bound(begin(b), end(b), x) - begin(b);
                int y = b[p] - b[p - 1] - 1;
                cout << (y + 1) / 2 << endl;
            }
        }
    }
    return 0;
}

C.Lazy Narek (dp)

题意:

N a r e k Narek Narek利用 C h a t G P T ChatGPT ChatGPT创作了 n n n道题,每道题由 m m m个字母组成,因此 N a r e k Narek Narek n n n个字符串。为了使问题更难,他选择一些字符串(可能没有)并将它们连接起来(不改变它们的顺序)来组合问题。他能解决该问题的几率定义为 s c o r e _ n − s c o r e _ c score\_n - score\_c score_nscore_c,其中 s c o r e _ n score\_n score_n N a r e k Narek Narek的得分, s c o r e _ c score\_c score_c C h a t G P T ChatGPT ChatGPT的得分。

N a r e k Narek Narek的目标是通过选择初始字符串中最优的子集来最大化 s c o r e _ n − s c o r e _ c score\_n - score\_c score_nscore_c的值。

分析:

d p [ i ] [ j ] dp[i][j] dp[i][j]表示考虑前 i i i个字符串,最后一组匹配了 j j j个字符时, s c o r e _ n − s c o r e _ c + j score\_n - score\_c+j score_nscore_c+j的值最大, j j j表示最后 j j j既不算入 N a r e k Narek Narek的字数,又不算入 G P T GPT GPT的分数。对前 i i i个字符串求出 l e n _ i len\_i len_i,表示前 i − 1 i-1 i1个字符串最后一组匹配 i i i个字符时,匹配完 s _ i s\_i s_i后最后一组匹配 l e n _ i len\_i len_i个字符,然后计算 s u m _ i sum\_i sum_i s u m _ i = s c o r e _ n − s c o r e _ c + l e n _ i sum\_i=score\_n - score\_c+len\_i sum_i=score_nscore_c+len_i。 我们在转移的时候枚举前 i − 1 i-1 i1个字符串最后一组的匹配字符数: d p [ i ] l e n [ j ] ] = d p [ i − 1 ] [ j ] + s u m [ j ] dp[i]len[j]]=dp[i-1][j]+sum[j] dp[i]len[j]]=dp[i1][j]+sum[j]

代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
#define endl '\n'
#define PII pair<LL, LL>
const int N = 3e5 + 10;
const int InF = 2e9 + 5;
const int mod = 1e9 + 7;
int tmp[500], vis[500];
char s[N];
int sum[5], len[5];
int n, m;
int dp[N][6];

void init(char s[ ])

{
    for (int i = 0; i <= 4; ++i)
        sum[i] = 0, len[i] = i;
    for (int i = 1; i <= m; ++i)
    {
        if (vis[s[i]])
        {
            for (int j = 0; j <= 4; ++j)
            {
                if (tmp[s[i]] == len[j] + 1)
                {
                    len[j]++;
                    if (len[j] == 5)
                        sum[j] += 5, len[j] = 0;
                }
                else
                    sum[j]--;
            }
        }
    }
}
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    int t;
    cin >> t;
    tmp['n'] = 1, tmp['a'] = 2, tmp['r'] = 3, tmp['e'] = 4, tmp['k'] = 5;
    vis['n'] = 1, vis['a'] = 1, vis['r'] = 1, vis['e'] = 1, vis['k'] = 1;
    while (t--)
    {
        cin >> n >> m;
        for (int i = 0; i <= n; ++i)
            for (int j = 0; j <= 5; ++j)
                dp[i][j] = -InF;
        dp[0][0] = 0;
        for (int i = 1; i <= n; ++i)
        {
            cin >> s + 1;
            init(s);
            for (int j = 0; j <= 4; ++j)
                dp[i][j] = dp[i - 1][j];
            for (int j = 0; j <= 4; ++j)
                dp[i][len[j]] = max(dp[i][len[j]], dp[i - 1][j] + sum[j]);
        }
        int ans = 0;
        for (int i = 0; i <= 4; ++i)
            ans = max(ans, dp[n][i] - i);
        cout << ans << endl;
    }
    return 0;
}


D.Alter the GCD (数学)

题意:

给出两个数组 a _ 1 , a _ 2 , … , a _ n a\_1, a\_2, \ldots, a\_n a_1,a_2,,a_n b _ 1 , b _ 2 , … , b _ n b\_1, b\_2, \ldots, b\_n b_1,b_2,,b_n

必须执行以下操作一次:

  • 选择 l l l r r r,使得 1 ≤ l ≤ r ≤ n 1 \le l \le r \le n 1lrn

  • 将此区间内的所有 a _ i a\_i a_i b _ i b\_i b_i交换。

在执行一次操作后,找到 gcd ( a _ 1 , a _ 2 , … , a _ n ) + gcd ( b _ 1 , b _ 2 , … , b _ n ) \text{gcd}(a\_1, a\_2, \ldots, a\_n) + \text{gcd}(b\_1, b\_2, \ldots, b\_n) gcd(a_1,a_2,,a_n)+gcd(b_1,b_2,,b_n)的最大可能值。同时找到实现最大值的不同对 ( l , r ) (l, r) (l,r)的数量。

分析:

首先我们考虑到,如果枚举出了两个端点,就可以方便的计算出区间的最大公约数。考虑到前缀 g c d gcd gcd的值的个数至多是 log ⁡ n \log n logn个。那么我们枚举 l , r l,r l,r,再分为三个区间 [ 0 , l − 1 ] , [ l , r − 1 ] , [ r , n − 1 ] [0,l−1],[l,r−1],[r,n−1] [0,l1],[l,r1],[r,n1]。对于 [ 0 , l − 1 ] [0,l−1] [0,l1]可以用前缀最大公约数,对于 [ r , n − 1 ] [r,n−1] [r,n1]我们可以用后缀最大公约数。对于中间的区间,首先我们考虑枚举右段点 r r r,然后对左端点,我们统计出前缀 g c d gcd gcd每个值出现最靠后的位置,共 l o g n logn logn个。然后我们可以计算出一个 c [ i ] c[i] c[i],表示 [ i , r − 1 ] [i,r−1] [i,r1]的区间 g c d gcd gcd,这个数组的值依旧只有 l o g log log个,因此我们可以像统计前缀 g c d gcd gcd每个值出现的最靠后的位置,统计出中间区间 g c d gcd gcd的值出现的最靠后的位置。这样的话,如果我们要维护这个数组的代价就是 l o g n logn logn的。 此时我们发现,对于当前的 r r r,其左端点可选的值为两个数组的前缀 g c d gcd gcd值出现的最靠后的位置,两个数组中间区间 g c d gcd gcd值出现的位置,一共只有 4 l o g n 4logn 4logn个,直接进行枚举就好。

代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
#define endl '\n'
#define PII pair<LL, LL>
const int N = 3e5 + 10;
const int InF = 2e9 + 5;
const int mod = 1e9 + 7;
map<PII, int> h[2];
int a[N], b[N], pa[N], pb[N], sa[N], sb[N];
int gcd(int a, int b)
{
    return !b ? a : gcd(b, a % b);
}
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    int t;
    cin >> t;
    while (t--)
    {
        int ans = 0;
        LL cnt = 0;
        int n;
        cin >> n;
        for (int i = 1; i <= n; ++i)
            cin >> a[i], pa[i] = gcd(pa[i - 1], a[i]);
        for (int i = 1; i <= n; ++i)
            cin >> b[i], pb[i] = gcd(pb[i - 1], b[i]);
        sa[n + 1] = sb[n + 1] = 0;
        for (int i = n; i; --i)
            sa[i] = gcd(sa[i + 1], a[i]), sb[i] = gcd(sb[i + 1], b[i]);
        for (int r = 1; r <= n; ++r)
        {
            int now = r & 1, lst = now ^ 1;
            for (auto [x, y] : h[lst])
                h[now][make_pair(gcd(x.first, b[r]), gcd(x.second, a[r]))] += y;
            h[now][make_pair(gcd(pa[r - 1], b[r]), gcd(pb[r - 1], a[r]))]++;
            h[lst].clear();
            for (auto [x, y] : h[now])
            {
                int w = gcd(x.first, sa[r + 1]) + gcd(x.second, sb[r + 1]);
                if (w > ans)
                    ans = w, cnt = y;
                else if (w == ans)
                    cnt += y;
            }
        }
        h[0].clear(), h[1].clear();
        for (int i = 1; i <= n; ++i)
            sa[i] = sb[i] = pa[i] = pb[i] = 0;
        cout << ans << " " << cnt << endl;
    }
    return 0;
}

E1.Subtangle Game (Easy Version) (dp)

题意:

T s o v a k Tsovak Tsovak N a r e k Narek Narek正在玩游戏。他们有一个数组 a a a和一个整数矩阵 b b b,该矩阵有 n n n行和 m m m列,从 1 1 1开始编号。第 i i i行和 j j j列的单元格是 ( i , j ) (i, j) (i,j)

他们轮流寻找 a a a的元素; T s o v a k Tsovak Tsovak先开始。每次玩家在矩阵中寻找包含当前元素 a a a的单元格( T s o v a k Tsovak Tsovak寻找第一个,然后 N a r e k Narek Narek寻找第二个,依此类推)。假设一名玩家选择了单元格 ( r , c ) (r, c) (r,c)。下一名玩家必须在从 ( r + 1 , c + 1 ) (r + 1, c + 1) (r+1,c+1)开始到 ( n , m ) (n, m) (n,m)结束的子矩阵中选择自己的单元格(如果是 r = n r=n r=n c = m c=m c=m,子矩阵可以为空)。如果一名玩家找不到这样的单元格(或剩余的子矩阵为空)或数组结束(前一名玩家选择了最后一个元素),那么他就输了。

确定玩家发挥最佳的情况下谁是赢家。

分析:

d f s ( i , j , k ) dfs(i, j, k) dfs(i,j,k)表示当前位于点 ( i , j ) (i,j) (i,j)且已经取完 k k k个数字下的先手状态,那么现在必须在 ( i + 1 , j + 1 ) (i + 1, j + 1) (i+1,j+1) ( n , m ) (n, m) (n,m)之间的点中选择 a [ k + 1 ] a[k + 1] a[k+1]。由此我们得到状态转移: 若存在 b [ x ] [ y ] = a [ k + 1 ] , x > i , y > j b[x][y] = a[k + 1],x > i, y > j b[x][y]=a[k+1],x>i,y>j,使得 d f s ( x , y , k + 1 ) = 0 dfs(x, y, k + 1) = 0 dfs(x,y,k+1)=0,则 d f s ( i , j , k ) = 1 dfs(i, j, k) = 1 dfs(i,j,k)=1。否则 d f s ( i , j , k ) = 0 dfs(i, j, k) = 0 dfs(i,j,k)=0。 直接记忆化搜索超出时间限制,根据状态转移进行贪心优化。可以发现对于同一个数值,优先取 ( x , y ) (x,y) (x,y)均更大位置会使得后手可选择的范围更小。故对于每个数值仅记录其可以选取的更优位置,从而减少矩阵重复数字较多情况下的状态转移。

代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
#define endl '\n'
#define PII pair<LL, LL>
const int N = 3e5 + 10;
const int InF = 2e9 + 5;
const int mod = 1e9 + 7;
const int maxn = 305;
int l, n, m, a[maxn], b[maxn][maxn];
int tmp[maxn][maxn][10];
vector<vector<pair<int, int>>> pos(10);
int dfs(int x, int y, int z)
{
    if (z == l)
        return 0;
    int &res = tmp[x][y][z];
    if (res != -1)
        return res;
    res = 0;
    for (auto &[i, j] : pos[a[z + 1]])
    {
        if (i > x && j > y && dfs(i, j, z + 1) == 0)
        {
            res = 1;
        }
    }
    return res;
}
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    int t;
    cin >> t;
    while (t--)
    {
        memset(tmp, -1, sizeof(tmp));
        for (int i = 0; i < 10; i++)
            pos[i].clear();
        cin >> l >> n >> m;
        for (int i = 1; i <= l; i++)
            cin >> a[i];
        for (int i = 1; i <= n; i++)
        {
            for (int j = 1; j <= m; j++)
            {
                cin >> b[i][j];
                int x = b[i][j];
                while (pos[x].size() && pos[x].back().second < j)
                {
                    pos[x].pop_back();
                }
                pos[x].push_back({i, j});
            }
        }
        if (dfs(0, 0, 0))
            cout << "T" << endl;
        else
            cout << "N" << endl;
    }
    return 0;
}

赛后交流

在比赛结束后,会在交流群中给出比赛题解,同学们可以在赛后查看题解进行补题。

群号: 704572101,赛后大家可以一起交流做题思路,分享做题技巧,欢迎大家的加入。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值