Codeforces Round 972 (Div. 2)

A. Simple Palindrome

给定整数 \(n\),构造长度为 \(n\) 的只由a e i o u的字符串,使得它的回文子序列最少。

容易发现 aia 不如 aai 优,贪心的将每种字符放在一起,并将总个数尽量均分到每个字符上。

点击查看代码
#include<bits/stdc++.h>
using namespace std;

#define ll long long
#define ull unsigned long long

int read()
{
    int x = 0; bool f = false; char c = getchar();
    while(c < '0' || c > '9') f |= (c == '-'), c = getchar();
    while(c >= '0' && c <= '9') x = (x << 1) + (x << 3) + (c & 15), c = getchar();
    return f ? -x : x;
}

void solve()
{
    int n = read();
    int k = n / 5, res = n % 5;
    for(int i = 1; i <= k; ++i) printf("a");
    if(res) {printf("a"); --res;}
    for(int i = 1; i <= k; ++i) printf("e");
    if(res) {printf("e"); --res;}
    for(int i = 1; i <= k; ++i) printf("i");
    if(res) {printf("i"); --res;}
    for(int i = 1; i <= k; ++i) printf("o");
    if(res) {printf("o"); --res;}
    for(int i = 1; i <= k; ++i) printf("u");
    printf("\n");

}

int main()
{
    int T = read();
    while(T--) solve();
    return 0;
}

B1. The Strict Teacher (Easy Version)

有一排 \(n\) 间房间,\(m\) 个老师,一个学生在逃课,最初所有人的位置互不相同,每一步每个人都可以留在原地或者向相邻的房间走,问最多需要几步抓住学生。

如果没有老师在学生的左边或者右边,那么学生可以跑到最左边或者最右边直到被抓住。

如果学生两边都有老师,设最近的两个老师中间的房子为 \(len\) ,每次都可以使 \(len - 2\) ,直到 \(len\) 小于等于 \(0\)

点击查看代码
#include<bits/stdc++.h>
using namespace std;

#define ll long long
#define ull unsigned long long

int read()
{
    int x = 0; bool f = false; char c = getchar();
    while(c < '0' || c > '9') f |= (c == '-'), c = getchar();
    while(c >= '0' && c <= '9') x = (x << 1) + (x << 3) + (c & 15), c = getchar();
    return f ? -x : x;
}

const int N = 2e5 + 5;
int a[N];

void solve()
{
    int n = read(), m = read(), q = read();
    for(int i = 1; i <= m; ++i) a[i] = read();
    sort(a + 1, a + m + 1);
    while(q--)
    {
        int pos = read();
        if(pos < a[1]) printf("%d\n", a[1] - 1);
        else if(pos > a[m]) printf("%d\n", n - a[m]);
        else
        {
            int id = upper_bound(a + 1, a + m + 1, pos) - a;
            int len = a[id] - a[id - 1] - 1;
            printf("%d\n", (len + 1) / 2);
        }
    }
}

int main()
{
    int T = read();
    while(T--) solve();
    return 0;
}

B2. The Strict Teacher (Hard Version)

\(B1\)

点击查看代码
#include<bits/stdc++.h>
using namespace std;

#define ll long long
#define ull unsigned long long

int read()
{
    int x = 0; bool f = false; char c = getchar();
    while(c < '0' || c > '9') f |= (c == '-'), c = getchar();
    while(c >= '0' && c <= '9') x = (x << 1) + (x << 3) + (c & 15), c = getchar();
    return f ? -x : x;
}

const int N = 2e5 + 5;
int a[N];

void solve()
{
    int n = read(), m = read(), q = read();
    for(int i = 1; i <= m; ++i) a[i] = read();
    sort(a + 1, a + m + 1);
    while(q--)
    {
        int pos = read();
        if(pos < a[1]) printf("%d\n", a[1] - 1);
        else if(pos > a[m]) printf("%d\n", n - a[m]);
        else
        {
            int id = upper_bound(a + 1, a + m + 1, pos) - a;
            int len = a[id] - a[id - 1] - 1;
            printf("%d\n", (len + 1) / 2);
        }
    }
}

int main()
{
    int T = read();
    while(T--) solve();
    return 0;
}

C. Lazy Narek

给定 \(n\) 个长度为 \(m\) 的字符串,一个人 \(A\)\(GPT\) 玩游戏,对于一个字符串 \(S\)

\(A\) 的策略是循环选取n a r e k,每选出一组,\(score_A += 5\),不足一组不算 \(A\) 选取的。

对于 \(A\) 没有选取的n a r e k,每一个字符使 \(score_{GPT} += 1\)

目标是,在不改变 \(n\) 的字符串的相对顺序的情况下,选取一些字符串依次连接构成 \(S\),使 \(score_A - score_{GPT}\) 最大。

容易发现秉持能匹配则匹配的原则一定最优。

发现只有5种字符有用,且字符串顺序不变,设 \(dp[i][j]\) 表示考虑前 \(i\) 个串,最后一组匹配了 \(j\) 个字符时,\(score_A - score_{GPT} + j\) 最大。

\(+ j\) 是最后 \(j\) 个字符既不算入 \(A\) 的分数,也不算入 \(GPT\) 的分数。

首先对第 \(i\) 个字符串 \(DP\) 求出 \(sum_i, len_i\),表示前 \(i-1\) 个字符串最后一组匹配了 \(i\) 个字符时,匹配完 \(s_i\) 后最后一组匹配了 \(len_i\) 个字符,\(sum_i = score_A - score_{GPT} + len_i\)

转移时枚举前 \(i-1\) 个字符串最后一组的匹配字符数。

\[dp[i][len[j]] = dp[i - 1][j] + sum[j] \]

点击查看代码
#include<bits/stdc++.h>
using namespace std;

#define ll long long
#define ull unsigned long long

int read()
{
    int x = 0; bool f = false; char c = getchar();
    while(c < '0' || c > '9') f |= (c == '-'), c = getchar();
    while(c >= '0' && c <= '9') x = (x << 1) + (x << 3) + (c & 15), c = getchar();
    return f ? -x : x;
}

const int inf = 0x3f3f3f3f;
const int N = 1e3 + 5;
int n, m;
int id[500], vis[500];
char s[N];
int sum[5], len[5];

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(id[s[i]] == len[j] + 1)
                {
                    len[j]++;
                    if(len[j] == 5) sum[j] += 5, len[j] = 0;
                }else sum[j]--;
            }
        }
    }
}

int dp[N][6];

void solve()
{
    n = read(), m = read();
    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)
    {
        scanf("%s", s + 1);
        init(s);
        // for(int j = 0; j <= 4; ++j) printf("sum = %d len = %d\n", sum[j], len[j]);
        // printf("\n");
        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);
    printf("%d\n", ans);
}

int main()
{
    id['n'] = 1, id['a'] = 2, id['r'] = 3, id['e'] = 4, id['k'] = 5;
    vis['n'] = 1, vis['a'] = 1, vis['r'] = 1, vis['e'] = 1, vis['k'] = 1;
    int T = read();
    while(T--) solve();
    return 0;
}

D. Alter the GCD

注意到一个序列的前缀 \(\operatorname{gcd}\) 最多变化 \(\log\) 次。

记选择翻转区间的 \(\gcd\)\(midA\)\(midB\),同理设前后缀 \(gcd\)\(preA, sufA, preB, sufB\)

考虑枚举选择翻转的段的左端点 \(l\),对于不同的 \(r\)\(midA, midB, sufA, sufB\) 都最多变化 \(\log\) 次。

那么对于不同的 \(r\) ,至多有 \(4\log\) 的位置会改变四种 \(\gcd\),每次二分找到下一次改变 \(\gcd\) 的位置即可。

对于答案统计,由于每一段左端点固定,右端点是一段区间且四种 \(\gcd\) 都不变,方案数即右端点所在区间长度。

二分+求\(\gcd\)+右端点\(\log\)次变化 = \(O(n\log^3n)\)

(这能过5e5?)

考虑如何优化,发现瓶颈在于每次需要二分找到四种 \(\gcd\) 变化的位置,由于是每次删去一个数使得无法维护 \(\gcd\),考虑倒序枚举左端点,每次增加一个数,可以从上一个左端点转移过来。

具体的,当左端点从 \(l + 1\)\(l\) 时,对于所有的中间区间 \(\gcd\) 需要再和 \(a[l]\)\(\gcd\)。由于这种 \(\gcd\) 最多有 \(\log n\) 个,所有每次可以 \(O(\log^2 n)\)维护。

对于所有的后缀区间 \(\gcd\) 只需要再加入一个 \(\gcd(a[l] \sim a[n])\)。每次 \(O(\log n)\) 维护。

总复杂度 \(O(n \log^2 n)\)

实现上,需要将四种 \(\gcd\) 取出来排序,为了方便使用了 \(\operatorname{sort}\) 函数,实际复杂度应为 \(O(n \log^2 n \log \log n)\)

$ n\log^3 n$ 代码
#include<bits/stdc++.h>
using namespace std;

#define ll long long
#define ull unsigned long long

int read()
{
    int x = 0; bool f = false; char c = getchar();
    while(c < '0' || c > '9') f |= (c == '-'), c = getchar();
    while(c >= '0' && c <= '9') x = (x << 1) + (x << 3) + (c & 15), c = getchar();
    return f ? -x : x;
}

const int N = 2e5 + 5;
int n, a[N], b[N];
int lg[N], stA[20][N], stB[20][N];

int gcd(int x, int y)
{
    if(!x || !y) return x | y;
    return gcd(y, x % y);
}

int getgcd(int l, int r, int d)
{
    if(l > r) return 0;
    int k = lg[r - l + 1];
    if(d == 0) return gcd(stA[k][l], stA[k][r - (1 << k) + 1]);
    if(d == 1) return gcd(stB[k][l], stB[k][r - (1 << k) + 1]);
    return 0;
}

void solve()
{
    n = read();
    for(int i = 1; i <= n; ++i) a[i] = read();
    for(int i = 1; i <= n; ++i) b[i] = read();
    for(int i = 1; i <= n; ++i) stA[0][i] = a[i], stB[0][i] = b[i];
    for(int i = 1; i <= 19; ++i)
        for(int j = 1; j + (1 << i) - 1 <= n; ++j)
        {
            stA[i][j] = gcd(stA[i - 1][j], stA[i - 1][j + (1 << (i - 1))]);
            stB[i][j] = gcd(stB[i - 1][j], stB[i - 1][j + (1 << (i - 1))]);
        }
    ll ans = 0, sum = 0;
    int preA = 0, preB = 0;
    for(int l = 1; l <= n; ++l)
    {
        int midA = a[l], sufA = getgcd(l + 1, n, 0), midB = b[l], sufB = getgcd(l + 1, n, 1);
        int last = l, L = l, R = n;
        while(L <= n)
        {
            while(L < R)
            {
                int mid = (L + R + 1) >> 1;
                if(midA == getgcd(l, mid, 0) && sufA == getgcd(mid + 1, n, 0) && midB == getgcd(l, mid, 1) && sufB == getgcd(mid + 1, n, 1)) L = mid;
                else R = mid - 1;
            }
            int tmp = gcd(preA, gcd(midB, sufA)) + gcd(preB, gcd(midA, sufB)), cnt = L - last + 1;
            // printf("l = %d, r = %d, preA = %d, midA = %d, sufA = %d, preB = %d, midB = %d, sufB = %d\n", l, L, preA, midA, sufA, preB, midB, sufB);
            // printf("tmp = %d, sum = %d\n", tmp, sum);
            if(tmp > ans) ans = tmp, sum = cnt;
            else if(tmp == ans) sum += cnt;
            ++L, R = n, last = L;
            midA = getgcd(l, L, 0), sufA = getgcd(L + 1, n, 0), midB = getgcd(l, L, 1), sufB = getgcd(L + 1, n, 1);
        }


        preA = gcd(preA, a[l]), preB = gcd(preB, b[l]);
    }
    printf("%lld %lld\n", ans, sum);
}

int main()
{
    lg[0] = -1;
    for(int i = 1; i <= 200000; ++i) lg[i] = lg[i >> 1] + 1;
    int T = read();
    while(T--) solve();
    return 0;
}
$n \log^2 n$ 代码
#include<bits/stdc++.h>
using namespace std;

#define ll long long
#define ull unsigned long long

int read()
{
    int x = 0; bool f = false; char c = getchar();
    while(c < '0' || c > '9') f |= (c == '-'), c = getchar();
    while(c >= '0' && c <= '9') x = (x << 1) + (x << 3) + (c & 15), c = getchar();
    return f ? -x : x;
}

const int N = 2e5 + 5;
int n, a[N], b[N];
int preA[N], preB[N], SUFA[N], SUFB[N];
vector< pair<int, int> > sufA, sufB, midA, midB;
vector< pair<int, int> > tmp;
int lg[N], stA[20][N], stB[20][N];

int gcd(int x, int y)
{
    if(!y) return x;
    return gcd(y, x % y);
}

void init()
{
    for(int i = 1; i <= n; ++i) stA[0][i] = a[i], stB[0][i] = b[i];
    for(int i = 1; i <= 19; ++i)
        for(int j = 1; j + (1 << i) - 1 <= n; ++j)
            stA[i][j] = gcd(stA[i - 1][j], stA[i - 1][j + (1 << (i - 1))]),
            stB[i][j] = gcd(stB[i - 1][j], stB[i - 1][j + (1 << (i - 1))]);
}

int getgcd(int l, int r, int d)
{
    if(l > r) return 0;
    int k = lg[r - l + 1];
    if(d == 0) return gcd(stA[k][l], stA[k][r - (1 << k) + 1]);
    else return gcd(stB[k][l], stB[k][r - (1 << k) + 1]);
}

int sta[N], top, vis[N];

void solve()
{
    n = read();
    for(int i = 1; i <= n; ++i) a[i] = read(), preA[i] = gcd(a[i], preA[i - 1]);
    for(int i = 1; i <= n; ++i) b[i] = read(), preB[i] = gcd(b[i], preB[i - 1]);
    SUFA[n + 1] = SUFB[n + 1] = 0;
    for(int i = n; i >= 1; --i) SUFA[i] = gcd(a[i], SUFA[i + 1]), SUFB[i] = gcd(b[i], SUFB[i + 1]);
    init();
    sufA.clear(), sufB.clear(), midA.clear(), midB.clear();
    int ans = 0;
    ll sum = 0;

    sufA.emplace_back(pair<int, int>(a[n], n));
    sufB.emplace_back(pair<int, int>(b[n], n));
    midA.emplace_back(pair<int, int>(a[n], n));
    midB.emplace_back(pair<int, int>(b[n], n));
    for(int l = n - 1; l >= 0; --l)
    {
        for(auto [val, id] : sufA) if(!vis[id - 1]) sta[++top] = id - 1, vis[id - 1] = 1;
        for(auto [val, id] : sufB) if(!vis[id - 1]) sta[++top] = id - 1, vis[id - 1] = 1;
        for(auto [val, id] : midA) if(!vis[id]) sta[++top] = id, vis[id] = 1;
        for(auto [val, id] : midB) if(!vis[id]) sta[++top] = id, vis[id] = 1;
        sort(sta + 1, sta + top + 1);
        if(!vis[n]) sta[++top] = n, vis[n] = 1;
        sta[++top] = n + 1;

        for(int i = 1; i < top; ++i)
        {
            int r = sta[i];
            if(r <= l) continue;
            int MIDA = getgcd(l + 1, r, 0), MIDB = getgcd(l + 1, r, 1);
            int ansA = gcd(preA[l], gcd(MIDB, SUFA[r + 1])), ansB = gcd(preB[l], gcd(MIDA, SUFB[r + 1]));
            if(ansA + ansB > ans){ ans = ansA + ansB, sum = sta[i + 1] - sta[i]; }
            else if(ansA + ansB == ans) sum += sta[i + 1] - sta[i];
        }

        for(int i = 1; i < top; ++i) vis[sta[i]] = 0;
        top = 0;

        if(!l) continue;
        pair<int, int> flag1 = *--sufA.end();
        int flag2 = gcd(flag1.first , a[l]); 
        if(flag1.first == flag2) sufA.pop_back();
        sufA.emplace_back(pair<int, int>(flag2, l));

        flag1 = *--sufB.end();
        flag2 = gcd(flag1.first , b[l]);
        if(flag1.first == flag2) sufB.pop_back();
        sufB.emplace_back(pair<int, int>(flag2, l));

        tmp.emplace_back(pair<int, int>(a[l], l));
        for(auto [val, id] : midA)
        {
            int newval = gcd(val, a[l]);
            if(newval == (*--tmp.end()).first ) continue;
            else tmp.emplace_back(pair<int, int>(newval, id));
        }
        midA = tmp, tmp.clear();

        tmp.emplace_back(pair<int, int>(b[l], l));
        for(auto [val, id] : midB)
        {
            int newval = gcd(val, b[l]);
            if(newval == (*--tmp.end()).first ) continue;
            else tmp.emplace_back(pair<int, int>(newval, id));
        }
        midB = tmp, tmp.clear();
    }
    printf("%d %lld\n", ans, sum);
}

int main()
{
    lg[0] = -1;
    for(int i = 1; i <= 200000; ++i) lg[i] = lg[i >> 1] + 1;
    int T = read();
    while(T--) solve();
    return 0;
}

E1. Subtangle Game (Easy Version)

\(dp[i][j][k]\) 表示第 \(i\) 步选择 \(b[j][k]\) 时是否有必胜策略。

转移时,枚举 \(l > j, t > k\)

若存在 \(dp[i + 1][l][t]\) 为真,则 \(dp[i][j][k]\) 为假。

若不存在,且 \(a[i] = b[j][k]\)(第 \(i\) 步可以选择 \(b[j][k]\)),则 \(dp[i][j][k]\) 为真。

发现可以二维前缀和优化,\(O(n^3)\) 过。

点击查看代码
#include<bits/stdc++.h>
using namespace std;

#define ll long long
#define ull unsigned long long

int read()
{
    int x = 0; bool f = false; char c = getchar();
    while(c < '0' || c > '9') f |= (c == '-'), c = getchar();
    while(c >= '0' && c <= '9') x = (x << 1) + (x << 3) + (c & 15), c = getchar();
    return f ? -x : x;
}

const int N = 305;
int l, n, m;
int a[N];
int b[N][N];
int dp[N][N][N];

void solve()
{
    l = read(), n = read(), m = read();
    for(int i = 1; i <= l; ++i) a[i] = read();
    for(int i = 1; i <= n; ++i)
        for(int j = 1; j <= m; ++j)
            b[i][j] = read();
    for(int i = 0; i <= l + 1; ++i)
        for(int j = 0; j <= n + 1; ++j)
            for(int k = 0; k <= m + 1; ++k)
                dp[i][j][k] = 0;
    for(int i = l; i >= 1; --i)
    {
        for(int j = n; j >= 1; --j)
            for(int k = m; k >= 1; --k)
            {
                if(b[j][k] == a[i])
                {
                    if(!dp[i + 1][j + 1][k + 1]) dp[i][j][k] = 1;
                }
                dp[i][j][k] = dp[i][j][k] + dp[i][j + 1][k] + dp[i][j][k + 1] - dp[i][j + 1][k + 1];
            }
    }
    if(dp[1][1][1]) printf("T\n");
    else printf("N\n");
}

int main()
{
    int T = read();
    while(T--) solve();
    return 0;
}

E2. Subtangle Game (Hard Version)

考虑 \(E1\)\(dp\) 做完前缀和后的新定义:第 \(i\) 步选择子矩阵 $b[j][k] \sim b[n][m] $ 中的数,是否有必胜策略。

发现:在存在可以选出前 \(i+2\) 步时,若 \(dp[i][j][k]\) 为真,\(dp[i+2][j][k]\) 也一定为真。

只需要对奇数和偶数分别考虑即可,设 \(dp0[j][k]\) 表示偶数步选择子矩阵 $b[j][k] \sim b[n][m] $ 中的数,最小的必胜步数(即最小的 \(i\)),\(dp1[j][k]\) 同理。

\(last[x]\) 表示值 \(x\)\(a\) 序列中第一次出现的位置。

转移时,若 \(last[b[i][j]] \% 2 = 1\),且 \(dp0[i+1][j+1] > last[b[i][j]]+1\),则 \(dp1[i][j] = last[b[i][j]]\)

意思是,第 \(last[b[i][j]]\) 步选了 \(b[i][j]\),且另一个人不存在 在不超过 \(last[b[i][j]]+1\) 步选矩阵 \(b[i+1][j+1] \sim b[n][m]\) 中的数 的情况下的必胜策略,那么这一步是必胜的,最小步数是 \(last[b[i][j]]\)

一点思考:同一个值在 \(a\) 序列中会出现多次,也会出现在奇数位和偶数位,为什么只考虑第一次出现?

\(b\) 矩阵中,同样的值,选择更靠近右下角的位置一定更优,因此对于 \(a\) 序列中的数,两个人是抢着选 \(b\),因此只考虑每种值第一次出现的位置。

点击查看代码
#include<bits/stdc++.h>
using namespace std;

#define ll long long
#define ull unsigned long long

int read()
{
    int x = 0; bool f = false; char c = getchar();
    while(c < '0' || c > '9') f |= (c == '-'), c = getchar();
    while(c >= '0' && c <= '9') x = (x << 1) + (x << 3) + (c & 15), c = getchar();
    return f ? -x : x;
}

const int inf = 0x3f3f3f3f;
const int N = 1505;
int l, n, m;
int a[N], b[N][N], last[N * N];
int dp0[N][N], dp1[N][N];

void solve()
{
    l = read(), n = read(), m = read();
    for(int i = 1; i <= l; ++i)
    {
        a[i] = read();
        if(!last[a[i]]) last[a[i]] = i;
    }
    for(int i = 1; i <= n; ++i)
        for(int j = 1; j <= m; ++j)
            b[i][j] = read();
    for(int i = 0; i <= n + 1; ++i)
        for(int j = 0; j <= m + 1; ++j)
            dp0[i][j] = dp1[i][j] = inf;
    for(int i = n; i >= 1; --i)
        for(int j = m; j >= 1; --j)
        {
            dp0[i][j] = min(dp0[i + 1][j], dp0[i][j + 1]);
            dp1[i][j] = min(dp1[i + 1][j], dp1[i][j + 1]);
            if(!last[b[i][j]]) continue;
            if(last[b[i][j]] % 2 == 0 && dp1[i + 1][j + 1] > last[b[i][j]] + 1) dp0[i][j] = min(dp0[i][j], last[b[i][j]]);
            if(last[b[i][j]] % 2 == 1 && dp0[i + 1][j + 1] > last[b[i][j]] + 1) dp1[i][j] = min(dp1[i][j], last[b[i][j]]);
        }
    if(dp1[1][1] == 1) printf("T\n");
    else printf("N\n");
    for(int i = 1; i <= l; ++i) last[a[i]] = 0;
}

int main()
{
    int T = read();
    while(T--) solve();
    return 0;
}
posted @ 2024-09-16 08:49  梨愁浅浅  阅读(1097)  评论(5编辑  收藏  举报