AtCoder Beginner Contest 135 解题报告

转载自:https://lornd.top/index.php/archives/9/

比赛地址:AtCoder Beginner Contest 135

A - Harmony

题目大意:给你两个数 \(A\)\(B\) ,是否存在一个整数 \(K\) 使得 \(|A - K| = |B - K|\)

解题思路:显然 \(K = \frac{A + B}{2}\) 。那么当 \(A\)\(B\) 奇偶性不同时,\(K\) 就不存在。

#include <cstdio>

int a, b;

int main() {
    scanf("%d%d", &a, &b);
    if ((a - b) & 1)
        printf("IMPOSSIBLE");
    else
        printf("%d", (a + b) >> 1);
    return 0;
}

B - 0 or 1 Swap

题目大意:给你一个 \(1 \sim n\) 的排列,是否可以通过一次对这个排列中的某两个元素的交换来使得这个排列是递增的。

解题思路:因为这个序列是 \(1 \sim n\) 的排列,所以当这个排列是递增的的时候,必然有 \(a_i = i\ (i \in [1,n])\) 。因此我们仅需统计有多少个 \(i\) 满足 \(a_i \neq i\) 即可,若大于两个,则无法满足题意。

#include <cstdio>

const int MAXN = 55;

int n, cnt;
int arr[MAXN];

int main() {
    scanf("%d", &n);
    for (int i = 1; i <= n; ++i) {
        scanf("%d", &arr[i]);
        if (arr[i] != i)
            ++cnt;
    }
    if (cnt > 2)
        printf("NO");
    else
        printf("YES");
    return 0;
}

C - City Savers

题目大意:有 \(n\) 个城市,第 \(i\) 个城市有 \(A_i\) 个怪物和一个勇士,在第 \(i\) 个城市的勇士可以消灭第 \(i\)\(i + 1\) 个城市的怪物共 \(B_i\) 个,问最多能消灭多少个怪物。

解题思路:简单贪心,因为下一个城市的怪物就算自己消灭不了,下一个城市的勇士也会去尽可能消灭,所以就先消灭完自己城市的怪物再去帮助下一个城市。

#include <cstdio>

typedef long long int ll;

const int MAXN = 1e5 + 5;

int n;
ll ans;
ll A[MAXN], B[MAXN];

int main() {
    scanf("%d", &n);
    for (int i = 1; i <= n + 1; ++i) {
        scanf("%lld", &A[i]);
    }
    for (int i = 1; i <= n; ++i) {
        scanf("%lld", &B[i]);
        if (B[i] <= A[i])
            ans += B[i];
        else if (B[i] <= A[i] + A[i + 1]) {
            ans += B[i];
            A[i + 1] -= (B[i] - A[i]);
        }
        else {
            ans += (A[i] + A[i + 1]);
            A[i + 1] = 0;
        }
    }
    printf("%lld", ans);
    return 0;
}

D - Digits Parade

题目大意:给你一个字符串 \(s\) ,由 \(0 \sim 9\)\(?\) 组成,每个 \(?\) 处可以填入 \(0 \sim 9\) 这是个数字,将所有数字填完之后,可以得到一个整数,问由多少种填法,使得这个数模 \(13\)\(5\)

解题思路:因为我们有以下定理:若 \(a\ Mod\ x = i,\ b\ Mod\ x = j,\ c\ Mod\ x = k\) ,那么 \((a \times b + c)\ Mod\ x = (i \times j + k)\ Mod\ x\) 。所以我们如果知道了一个数模 \(13\) 的余数,我们也能很快知道在这个数后面加上一个数字(将这个数乘 \(10\) 再加上一个一位数)模 \(13\) 的余数,所以我们假设 \(dp[i][j]\) 表示 考虑到第 \(i\) 位,前面能组成模 \(13\)\(j\) 的方案数,从前往后模拟这个过程即可,因为 \(j\) 很小,所以这个算法的复杂度可以认为是 \(\Theta(n)\)

#include <cstdio>
#include <cstring>

const int MAXN = 1e5 + 5;
const int mod = 1e9 + 7;

int len;
int dp[MAXN][13];
char s[MAXN];

int main() {
    scanf("%s", s + 1);
    len = strlen(s + 1);
    dp[0][0] = 1;
    for (int i = 1; i < 13; ++i) {
        dp[0][i] = 0;
    }
    for (int i = 0; i < len; ++i) {
        for (int j = 0; j < 13; ++j) {
            if (s[i + 1] != '?') {
                int tmp = s[i + 1] - '0';
                dp[i + 1][(j * 10 + tmp) % 13] += dp[i][j];
                if (dp[i + 1][(j * 10 + tmp) % 13] > mod)
                    dp[i + 1][(j * 10 + tmp) % 13] -= mod;
            }
            else {
                for (int k = 0; k <= 9; ++k) {
                    dp[i + 1][(j * 10 + k) % 13] += dp[i][j];
                    if (dp[i + 1][(j * 10 + k) % 13] > mod)
                        dp[i + 1][(j * 10 + k) % 13] -= mod;
                }
            }
        }
    }
    printf("%d", dp[len][5]);
    return 0;
}

E - Golf

题目大意:有一个球在点 \((0,0)\) ,要到点 \((x,y)\) 去。每次只能移动到与其所在点曼哈顿距离为 \(k\) 的点,问是否能到达点 \((x,y)\) ,若能,最少需要移动多少次?

解题思路:来自:Geothermal(红名)

由于平面直角坐标系的对称性,不失一般性地,我们认为点 \((x,y)\) 在第一象限,若在其他象限,只需对 \(x,y\) 进行乘 \(-1\)\(1\) 的变换即可。我们假设最少需要移动 \(n\) 次,其中正向移动(向着 \(x\) 轴和 \(y\) 轴的正方向移动)的距离为 \(a\) ,反向移动的距离为 \(b\) 。那么显然会有 \(a + b = nk\)\(a - b = x + y\) ,可以解得 \(a = \frac{nk + x + y}{2}, b = \frac{nk - x - y}{2}\)

因为 \(a,b\) 均为非负整数,所以我们可以得到三个结论:

  1. 如果 \(k\) 是偶数而 \(x + y\) 是奇数,那么一定不能到达,因为在这种话情况下无论 \(n\) 为多少,\(nk\) 均为偶数,\(nk \pm (x + y)\) 是奇数, \(a,b\) 均不是整数。

  2. 一定存在 \(nk \ge x + y\)\(nk \equiv (x + y)\ (Mod\ 2)\) ,前者是因为 \(b\) 的非负性,而后者是因为 \(a,b\) 要是整数。

  3. 如果 \(n = 1\) ,那么一定要 \(k = x + y\) ,这十分显然,因为如果点 \((x,y)\) 与点 \((0,0)\) 的曼哈顿距离不是 \(k\) ,那么肯定不能一步到达。

根据结论 \(1\) ,我们就可以判断出无解的情况了,接下来我们构造出结论 \(2, 3\) 所述情况的方法。

首先 \(n\) 可以暴力求出来,然后便可以算出 \(a\)\(b\) 。接下来我们先考虑反向移动,如果剩余反向移动的距离大于 \(k\) ,那么就在某一个方向上反向移动 \(k\) ,否则移动剩余的反向移动距离,在另一个方向上正向移动。接下来考虑正向移动,这个就可以随便移了,可以先让 \(x\) 到位再竖直移动,也可以先让 \(y\) 到位再水平移动,但最后总是可以到达 \((x,y)\) 这个点。

注意:反向移动所说的 “某一个方向” 指的都是在当前位置与目标地点更近的方向(不考虑另外一个方向的距离),即 \(min(|x_1 - x_2|, |y_1 - y_2|)\) 所对应的距离。

#include <cstdio>

typedef long long int ll;

int n, x, y, flagX, flagY, curX,  curY;
ll k, positive, negative;

int main() {
    scanf("%lld%d%d", &k , &x, &y);
    flagX = (x < 0) ? -1 : 1;
    flagY = (y < 0) ? -1 : 1;
    x *= flagX;
    y *= flagY;
    if (k % 2 ==0 && (x + y) & 1)
        printf("-1");
    else {
        if (k == x + y)
            n = 1;
        else {
            n = 2;
            while (n * k < x + y || (n * k - x - y) & 1) {
                ++n;
            }
        }
        positive = (n * k + x + y) >> 1;
        negative = (n * k - x - y) >> 1;
        printf("%d\n", n);
        while(n --) {
            if (negative) {
                if (negative >= k) {
                    if (x - curX <= y - curY)
                        curX -= k;
                    else
                        curY -= k;
                    negative -= k;
                }
                else {
                    if (x - curX <= y - curY) {
                        curX -= negative;
                        curY += (k - negative);
                    }
                    else {
                        curY -= negative;
                        curX += (k - negative);
                    }
                    negative = 0;
                }
            }
            else {
                if (curX < x) {
                    if (x - curX >= k)
                        curX += k;
                    else {
                        curY += (k - (x - curX));
                        curX = x;
                    }
                }
                else {
                    if (y - curY * flagY >= k)
                        curY += k;
                    else
                        curY = y;
                }
            }
            printf("%d %d\n", curX * flagX, curY * flagY);
        }
    }
    return 0;
}

F - Strings of Eternity

题目大意:给定两个字符串 \(S\)\(T\) ,找出最大的 \(i\) ,使得存在一个 \(j\) ,使得将 \(T\) 重复 \(i\) 次后得到的字符串是将 \(S\) 重复 \(j\) 次后所得的字符串的子串。显然,对于任何情况,总存在 \(i = 0\) 满足上述条件,若不存在最大的 \(i\) ,输出 \(-1\)

解题思路:来自:Geothermal(红名)

我们可以认为 \(j\) 是无限大,因为我们不断重复字符串 \(S\) 的过程中, \(i\) 只有可能越变越大,而不会越变越小。

因此,我们只需要找到这个无限长的字符串中所有与 \(T\) 匹配的子串,并找出最长的连续的多个子串即可。

我们发现:两个子串连续,假设它们的开头的下标分别为 \(p_1,p_2\ (p_1 < p _2)\) ,则一定有 \(p_1 + | T | = p_2\) 。因此我们如果可以找到所有子串的开头,再依照这个关系将所有开头连成一个图,问题就会被我们转化成一个有向图的最长路问题。

我们如何找到这些开头呢?因为 \(S\) 串是不断重复的,所以开头看起来有无数个,但事实上都可以映射到 \([1,| S |]\) 中,所以我们只需找到 \([1, |S|]\) 中所有的开头即可,为了保证所有的开头都能够被找到,我们只需不断重复 \(S\) 串,直到其长度大于最初的 \(|S| + |T|\) ,再对新的 \(S\) 串和 \(T\) 串进行 KMP 匹配即可。

在找到所有开头之后,下一步就是建图了,根据我们的发现,我们把所有开头的下标抽象成一个个点,在两个点之间连边,当且仅当两个下标在模 \(|S|\) 的意义下相差 \(|T|\) 。即 \(j\ Mod\ |S| = (i + |T|)\ Mod\ |S|\) ,前提是 \(i,j\) 均为我们所说的开头,这个时候,就连一条边,从 \(i\) 指向 \(j\)

接下来是寻找最长路,这就是一个经典题目了,拓扑排序 + DP 即可完成,但需要特判有环的情况,这也同样可以使用拓扑排序完成,只要完成拓扑排序之后,被排序的点的数量小于开头的总数,图中便一定有环,此时答案就是 \(-1\) ,否则答案就是最长路的长度 。

#include <cstdio>
#include <cstring>
#include <vector>
#include <queue>

const int MAXN = 5e5 + 5;

char s[MAXN << 2], t[MAXN];
int lenS, lenT, copyLenS, copyLenT, ans;
int next[MAXN], sum;
int entry[MAXN], res[MAXN], dp[MAXN];
bool find[MAXN << 2];

std::vector<int> con[MAXN];
std::queue<int> q;

void init() {
    int place = 0;
    next[1] = 0;
    for (int i = 2; i <= lenT; ++i) {
        while (place && t[i] != t[place + 1]) {
            place = next[place];
        }
        if (t[i] == t[place + 1])
            ++place;
        next[i] = place;
    }
}

void kmp() {
    init();
    int place = 0;
    for (int i = 1; i <= lenS; ++i) {
        while(place && s[i] != t[place + 1]) {
            place = next[place];
        }
        if (s[i] == t[place + 1])
            ++place;
        if (place == lenT && i - lenT + 1 <= copyLenS) {
            find[i - lenT + 1] = true;
            place = next[place];
            ++sum;
        }
    }
}

void ins(int start,int end) {
    con[start].push_back(end);
    ++entry[end];
}

bool topSort() {
    int cnt = 0;
    for (int i = 1; i <= copyLenS; ++i) {
        if (find[i] && entry[i] == 0)
            q.push(i);
    }
    while (!q.empty()) {
        int cur = q.front();
        res[++cnt] = cur;
        for (int i = 0; i < con[cur].size(); ++i) {
            if (--entry[con[cur][i]] == 0)
                q.push(con[cur][i]);
        }
        q.pop();
    }
    return cnt == sum;
}

int main() {
    scanf("%s%s", s + 1, t + 1);
    copyLenS = lenS = strlen(s + 1);
    copyLenT = lenT = strlen(t + 1);
    while (copyLenT > 0) {
        for (int i = 1; i <= copyLenS; ++i) {
            s[++lenS] = s[i];
        }
        copyLenT -= copyLenS;
    }
    kmp();
    for (int i = 1; i <= copyLenS; ++i) {
        int k = ((i + lenT) % copyLenS == 0) ? copyLenS : ((i + lenT) % copyLenS);
        if (find[i] && find[k])
            ins(i, k);
    }
    if (topSort()) {
        for (int k = 1; k <= sum; ++k) {
            int i = res[k];
            if (dp[i] == 0)
                dp[i] = 1;
            for (int j = 0; j < con[i].size(); ++j) {
                dp[con[i][j]] = std::max(dp[con[i][j]], dp[i] + 1);
            }
        }
        for (int i = 1; i <= sum; ++i) {
            ans = std::max(ans, dp[res[i]]);
        }
    }
    else
        ans = -1;
    printf("%d", ans);
    return 0;
}
posted @ 2019-08-05 13:23  lornd  阅读(553)  评论(0编辑  收藏  举报