Codeforces Round #666 Div 2 A-E题解

A-E题题解

比赛链接

A. Juggling Letters

题意:

给定\(n\)个字符串,每次可以交换任意两个字符串的两个字符,问能否将这\(n\)个字符串都变得相同?

思路:

对所有\(n\)个字符串的每字母进行桶排序,如果存在某一种字母的个数\(\bmod n \neq 0\),那么答案就是$NO \(,否则就是\)YES$。

代码:

#include <cstdio>
#include <cstring>
#include <algorithm>

const int maxn = 1005;

using namespace std;
int T, n, len;
char s[maxn];
int buck[26];

int main() {
    scanf("%d", &T);
    while (T--) {
        memset(buck, 0, sizeof(buck));
        scanf("%d", &n);
        for (int i = 0; i < n; i++) {
            scanf("%s", s);
            len = strlen(s);
            for (int i = 0; i < len; i++)
                buck[(int)s[i] - 'a']++;
        }

        int flag = 1;
        for (int i = 0; i < 26; i++)
            if (buck[i] % n) flag = 0;
        
        if (flag)
            printf("YES\n");
        else
            printf("NO\n");
    }
    return 0;
}

B. Power Sequence

题意:

如果对于数组\(a_0 \sim a_{n-1}\),存在一个正整数\(c\),满足\(a_i = c^i\),那么称数组\(a\)是一个power sequence​。我们可以将数组随意排序。我们每次操作可以将某个\(a_i\)加一或减一,问我们要把数组\(a\)变成power sequence,至少要操作多少次?

思路:

根据题意,我们先把数组\(a\)从小到大排序。

指数的增长非常快,题目数据\(a_i \leqslant 10^9\),我们考虑\(y=2^x\)的图像,当\(x > 63\)时,显然就已经会爆掉long long了。而此时,对于最后一个数,它的操作数显然要超出long long的范围。那么此时,显然我们把她们都变成\(1\)要更优(都变成\(1\)是不会爆long long的)。所以,当\(n \geqslant 63\)时,我们令\(ans = \sum\limits_{i=1}^{n} (a_i - 1)\)

\(n < 63\)时,我们考虑最后一个数(也就是最大的数)。我们容易想到,我们要找一个\(x\),满足\(x^{n-1} = a_n\)。那么\(x = \sqrt[n-1]{a_n} = e^{\tfrac{1}{n-1}\ln{a_n}}\)。所以我们从\(1\)\(\left\lfloor e^{\tfrac{1}{n-1}\ln{a_n}} \right\rfloor + 1\)枚举\(x\),然后计算操作次数即可。

代码:

#include <cstring>
#include <cstdio>
#include <algorithm>
#include <cmath>
typedef long long ll;

using namespace std;
const int maxn = 1e5 + 5;

int n;
ll a[maxn], ans;

ll fpow(ll a, ll b) {
    ll res = 1;
    while (b) {
        if (b & 1) res *= a;
        b >>= 1;
        a *= a;
    }
    return res;
}

int main() {
    scanf("%d", &n);
    for (int i = 0; i < n; i++)
        scanf("%lld", a + i);
    
    sort(a, a + n);

    if (n >= 64) {
        for (int i = 0; i < n; i++)
            ans += (a[i] - 1);
        
        printf("%lld\n", ans);
        return 0;
    }

    ans = 0x5f5f5f5f5f5f5f5f;
    ll tmp = 0;

    ll upbound = exp(log(a[n - 1]) / (n - 1)) + 1;
    for (int i = 1; i <= upbound; i++) {
        tmp = 0;
        for (int j = 0; j < n; j++) {
            tmp += abs(a[j] - fpow(i, j));
        }
        ans = min(ans, tmp);
    }
    printf("%lld\n", ans);
    return 0;
}

C. Multiples of Length

题意:

给定一个长度为\(n\)的数组\(a\)。你必须恰好进行如下操作三次:

  • 选择数组的一段\([l, r]\),给每个数都加上(可以是负数或则\(0\))一个数。这个数必须是\(r - l + 1\)的倍数。

你必须通过这三次操作把数组的所有数都变成\(0\)

思路:

考虑数论的知识。首先,我们可以选定一个数,然后把它变成\(0\)(直接加上\(-a_i\)就可以了)。然后,显然,\(n\)\(n - 1\)只相差\(1\)。那么,第一次操作,我们先将\(a_i\)变成\(0\);第二次操作,我们把\(a_2 \sim a_n\)分别加上\((n - 1)\cdot a_i\),此时他们都变成了\(n\cdot a_i\);第三次操作,我们把\(a_1 \sim a_n\)都减去他们自身就可以了(此时它们都是\(n\)的倍数,\(a_1 = 0\),也是\(n\)的倍数)。

注意\(n = 1\)的情况。

代码:

#include <cstdio>
#include <algorithm>
#include <cstring>
typedef long long ll;
using namespace std;
const int maxn = 1e5 + 5;
ll n;
ll a[maxn], b[maxn];

int main() {
    scanf("%lld", &n);
    for (int i = 1; i <= n; i++)
        scanf("%lld", a + i);

    if (n == 1) {
        printf("1 1\n%lld\n1 1\n0\n1 1\n0\n", -a[1]);
        return 0;
    }
    
    printf("1 1\n");
    printf("%lld\n", -a[1]);

    printf("2 %lld\n", n);
    for (int i = 2; i <= n; i++) {
        printf("%lld ", a[i] * (n - 1));
    }
    putchar('\n');

    printf("1 %lld\n", n);
    printf("0");
    for (int i = 2; i <= n; i++) {
        printf(" %lld", -a[i] * n);
    }
    putchar('\n');
    return 0;
}

D. Stoned Game

题意:

T和HL在玩游戏,T先手。有\(n\)堆石头,每堆石头有\(a_i\)个。他们每次可以选择某一堆石头,取走其中的一个。玩家不能选择上一回合被选择的那一堆石头(一定是被对手选的;或者是第一局,没有人选,则所有的石头堆都可以选)。如果某一回合,玩家没有石头可以取了,那么它就输了。两人都一直取最优策略,问最后谁

思路:

对于两个玩家来说,每次取能取的石头堆中石头最多的那一堆,一定是最优的,维护一个优先队列就可以了。证明本蒟蒻不会QwQ,请感性理解一下。

代码:

比赛的时候代码写的比较随意QwQ

#include <cstring>
#include <cstdio>
#include <algorithm>
#include <queue>

using namespace std;
typedef long long ll;

struct P {
    int val, id;	// id用来存储这一堆石头上一次被选择是哪一回合
    P(int val = 0, int id = 0): val(val), id(id) {}
    bool operator < (const P &rhs) const {
        return val < rhs.val;
    }
};

int T, n;
ll ans[2];

int main() {
    scanf("%d", &T);
    while (T--) {
        int cur = 2;	// cur = 2是因为从0开始计数的话,id会出错;而且从2开始下面判断也方便。
        ans[0] = ans[1] = 0;
        scanf("%d", &n);
        priority_queue<P> p;
        for (int i = 0; i < n; i++) {
            int x;
            scanf("%d", &x);
            if (x > 0)
                p.push(P(x, 0));
        }

        while (!p.empty()) {
            P u = p.top();
            p.pop();
            if (u.id <= cur - 2) {	// 上面说的方便就是这里,不用特判id == 0
                u.id = cur;
                u.val--;
                if (u.val)
                    p.push(u);
            }
            else {
                if (p.empty()) break;
                P u2 = p.top();
                p.pop();
                p.push(u);

                u2.id = cur;
                u2.val--;
                if (u2.val)
                    p.push(u2);
            }
            cur++;
        }

        if (cur & 1)
            printf("T\n");
        else
            printf("HL\n");
    }
    return 0;
}

E. Monster Invaders

题意:

有一个游戏,有\(n\)层,第\(i\)层有\(a_i\)个小怪,\(1\)个Boss。每个小怪\(1\)滴血,每个Boss\(2\)滴血。一开始你在第一层。你有三种枪:

  • 手枪:打一个目标一滴血,装填时间为\(r_1\)
  • 激光枪:对该层所有目标打一滴血(包括Boss),装填时间为\(r_2\)
  • AWP:直接杀死一个怪(可以直接杀死Boss),装填时间为\(r_3\)

如果你对Boss造成了伤害,但是没有打死,那么你必须转移到相邻的一层。转移的时间为\(d\)。你也可以随时跑到相邻的一层,花费时间为\(d\)。当你没有清掉某一层的小怪的时候,你不能打Boss(除了用激光枪)。问要把所有Boss清掉,你最少要花费多少时间?

思路:

这题麻烦就麻烦在,如果一枪干不死Boss,就必须要转移,所以我们考虑把状态都存下来。因为转移的时候,我们只能转移到相邻的层,所以,如果我们在第\(1\)层给Boss留了一滴血,我们打到第\(n\)层以后再返回来,此时\(2 \sim n - 1\)层我们就都经过了两次,这种情况显然比较差。此时就不如我们在第二层的时候,就倒回去把第一层清理掉。

我们考虑用\(dp[i][j]\)来存储到达第\(i\)层,Boss还剩\(j\)滴血时,我们花费的时间(开\(dp[n + 5][2]\)的数组就可以了)。那么,\(dp[1][0] = \operatorname{min}(r_1, r_3) \cdot a_1 + r_3, dp[1][1] = \operatorname{min}(r_2, (a[1] + 1) \cdot r_1)\)

然后我们考虑转移,转移一共考虑四种情况:

  1. 上一层全清,当前层也全清;
  2. 上一层全清,当前层Boss剩\(1\)滴血;
  3. 上一层Boss剩\(1\)滴血,当前层全清;
  4. 上一层Boss剩\(1\)滴血,当前层Boss也剩\(1\)滴血。

情况1和情况2都比较好考虑,直接从\(dp[i - 1][0]\)加过来就好了。麻烦的是情况3和4。

情况3:

要全清当前层,我们只能用min(手枪,AWP)清掉小怪,然后用AWP干掉Boss,然后转移一次,用min(手枪,激光枪,AWP)干掉上一层的Boss(因为只剩下一滴血了),然后再转移回来。需要额外转移两次。

注意,如果\(i = n\)(也就是最后一层)的话,我们就不需要再转移回来了,此时只需要额外转移一次。

情况4:

我们还可以把当前层Boss打残血,因为上一层Boss也残血,同时我们也不得不转移,所以就干脆转移到上一层,顺便把Boss干掉,然后再转移回来,把Boss也干掉。也是需要额外转移两次。

代码:

#include <cstdio>
#include <algorithm>
#include <cstring>
typedef long long ll;

using namespace std;
const int maxn = 1e6 + 5;
ll n, r[4], d, a[maxn];
ll dp[maxn][2];     // dp[i][0]: All cleared; dp[i][1]: Boss alive.

inline ll min(const ll &a, const ll &b, const ll &c) {
    ll t = min(a, b);
    return min(t, c);
}

int main() {
    scanf("%lld%lld%lld%lld%lld", &n, r, r + 1, r + 2, &d);
    for (int i = 1; i <= n; i++)
        scanf("%lld", a + i);

    dp[1][0] = a[1] * min(r[0], r[2]) + r[2];
    dp[1][1] = min(r[1], a[1] * min(r[0], r[2]) + r[0]);

    for (int i = 2; i <= n; i++) {
        dp[i][0] = dp[i - 1][0] + a[i] * min(r[0], r[2]) + r[2] + d;
        dp[i][1] = dp[i - 1][0] + min(r[1], (a[i] + 1) * r[0]) + d;
        
        ll tmp = dp[i - 1][1] + min(r[0], r[1], r[2]) + d;
        if (i < n)
            dp[i][0] = min(dp[i][0], a[i] * min(r[0], r[2]) + r[2] + d + tmp + d);  // This stage cleared, but previous stage not cleared.
        else
            dp[i][0] = min(dp[i][0], a[i] * min(r[0], r[2]) + r[2] + d + tmp);
        dp[i][0] = min(dp[i][0], min(r[1], (a[i] + 1) * r[0]) + d + tmp + d + r[0]);    // This stage not cleared, and previous stage not cleared.
    }

    printf("%lld\n", min(dp[n][0], dp[n][1] + d * 2 + min(r[0], r[1], r[2])));
    return 0;
}
posted @ 2020-08-31 12:31  icysky  阅读(212)  评论(0编辑  收藏  举报