AtCoder Beginner Contest 172

A - Calc (abc172 a)

题目大意

给定一个\(a\),输出 \(a + a^2 + a^3\)

解题思路

模拟即可。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;

int main(void) {
    ios::sync_with_stdio(false); 
    cin.tie(0); cout.tie(0);
    int a;
    cin >> a;
    cout << a + a * a + a * a * a << '\n';

    return 0;
}



B - Minor Change (abc172 b)

题目大意

给定两个字符串\(s\)\(t\),问同位置下不同字符的位置数。

解题思路

模拟即可。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;

int main(void) {
    ios::sync_with_stdio(false); 
    cin.tie(0); cout.tie(0);
    string s, t;
    cin >> s >> t;
    int ans = 0;
    for(int i = 0; i < s.size(); ++ i)
        ans += (s[i] != t[i]);
    cout << ans << '\n';

    return 0;
}



C - Tsundoku (abc172 c)

题目大意

给定两个数组\(A,B\)和一个数\(s\),从 \(A\)中取 \(x\)个数,从 \(B\)中取 \(y\)个数,要求这 \(x+y\)个数的和不超过\(s\),且 \(x+y\)最大。

解题思路

给两个数组从小到大排个序,然后枚举\(x\),二分找到最大的\(y\),取最大的 \(x+y\)即可。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;

int main(void) {
    ios::sync_with_stdio(false); 
    cin.tie(0); cout.tie(0);
    int n, m;
    LL k;
    cin >> n >> m >> k;
    vector<LL> a(n), b(m);
    for(auto &i : a)
        cin >> i;
    for(auto &i : b)
        cin >> i;
    partial_sum(a.begin(), a.end(), a.begin());
    partial_sum(b.begin(), b.end(), b.begin());
    int ans = upper_bound(b.begin(), b.end(), k) - b.begin();
    for(int i = 0; i < n; ++ i){
        if (a[i] > k)
            break;
        ans = max(ans, i + 1 + int(upper_bound(b.begin(), b.end(), k - a[i]) - b.begin()));
    }
    cout << ans << '\n';

    return 0;
}



D - Sum of Divisors (abc172 d)

题目大意

定义\(f(x)\)\(x\)的正因数的个数。

给定 \(n\),求 \(\sum_{k=1}^{n}k \times f(k)\)

解题思路

问题求一个数,所有因数的个数,乘以该数,求和。

转换下求和,考虑因数贡献,即对于一个因数\(x\),对于所有其倍数 \(y\),都会对答案贡献 \(y\)

因此枚举因数,再枚举其倍数,求其倍数和即可。

时间复杂度为\(O(n\log n)\)

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;

int main(void) {
    ios::sync_with_stdio(false); 
    cin.tie(0); cout.tie(0);
    int n;
    cin >> n;
    LL ans = 0;
    for(int i = 1; i <= n; ++ i){
        for(int j = i; j <= n; j += i)
            ans += j;
    }
    cout << ans << '\n';

    return 0;
}



E - NEQ (abc172 e)

题目大意

要求统计俩数组对\((A,B)\)的数量,满足长度均为\(n\)\(A,B\)数组中出现的数都在\([1,m]\) 之间,且俩俩互不相同,且对于下标\(i\) ,要求\(A_i \neq B_i\)

解题思路

对于数组\(A\),有 \(A_{m}^{n}\)种取法,然后考虑数组 \(B\)有多少种取法。

因为数组\(A\)的所有取法都可以视为等价的,因此考虑数组 \(B\)时,可以认为数组 \(A\)\(1,2,..,n\)

此时相当于一个类错排问题,即从 \([1,m]\)中取 \(n\)个数出来,要求第 \(i\)个位置上不能是数字 \(i\)

对于原错排问题,即从 \([1,i]\)中取 \(i\) 个数出来,要求第 \(j\)个位置上不能是数字 \(j\)。其答案\(dp_i = (i - 1)(dp_{i - 1} + dp_{i - 2}\) 。分别为

  • 考虑原\([1,i-1]\)子问题中的排列,将第 \(i\)个数, 放在第\(i\)位,然后再选择前面的 \(i-1\)中的一个位置交换,得到一个新排列。
  • 考虑原\([1,i-2]\)子问题中的排列,第\(i\)个数放在第\(i\)位,第\(i-1\)个数放在第\(i-1\)位,然后交换这两个数,得到一个新排列。显然这第\(i-1\)个数不一定是第 \(i-1\)个数,它可以是前面任意一个。

在该问题多了\(m-n\)个数,它不受任意条件限制。我们同样考虑递推求\(dp_i\)的值。

  • 如果第 \(i\)个位置放 \(i\),那么和上面的计算方式一样。

  • 如果第 \(i\)个位置放不受限制的数,有 \(m-n\)个数可以选,放了之后,第 \(i\)个数也变成不受限制的数了,此时之后的状态,不受限制的数还是 \(m-n\)个。

因此\(dp_i = (m - n) dp_{i - 1} + (i - 1)(dp_{i - 1} + dp_{i - 2})\)

答案就是\(A_{m}^{n}dp_n\)

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;

const int mo = 1e9 + 7;

int main(void) {
    ios::sync_with_stdio(false); 
    cin.tie(0); cout.tie(0);
    int n, m;
    cin >> n >> m;
    vector<int> dp(n + 1);
    dp[0] = 1;
    dp[1] = m - n;
    for(int i = 2; i <= n; ++ i){
        dp[i] = (1ll * (m - n) * dp[i - 1] % mo + 1ll * (i - 1) * (dp[i - 1] + dp[i - 2]) % mo) % mo;
    }
    int ans = dp[n];
    for(int i = m - n + 1; i <= m; ++ i){
        ans = 1ll * ans * i % mo;
    }
    cout << ans << '\n';

    return 0;
}



F - Unfair Nim (abc172 f)

题目大意

给定\(n\)堆石子,俩人玩 Nim游戏,即每次可从一堆石子取走至少一个石子,不能操作者输。

现在可以从第\(1\)堆拿一些石子放到第\(2\)堆(可以不拿,但不能拿空), 问拿走石子数的最小值,使得后手必胜。或告知不可行。

解题思路

根据\(sg\)理论,全部石子数异或和为 \(0\)则后手必胜。假设前两堆石子数和为\(x\), 后面的石子数异或和为 \(y\) ,则题意转换为

\(a\)\(b\),使得 \(a+b = x, a\oplus b = y\) ,且\(a\)不能超过第一堆石子数。最大化\(a\)

神奇的是 \(a+b = (a\oplus b) + 2(a \& b)\) ,即加法分为了不进位的加法进位两部分。

这样原问题进一步变为\(a \& b = z, a \oplus b = y\),其中 \(z = \frac{x - y}{2}\)

注意如果\(x-y\)是小于 \(0\)或是 奇数则无解。

此时两个运算都是位运算,我们可以逐位考虑。对于\(z\)\(y\)的某一位的情况:

  • 如果是 \(1,1\),则 无解,无法做到\(x \& y = 1, x \oplus y = 1\)
  • 如果是 \(1,0\),则 \(a,b\)在该位都必须为\(1\)
  • 如果是 \(0,1\),则 \(a,b\)仅有一个在该位为 \(1\)
  • 如果是 \(0,0\),则 \(a,b\)在该位都必须为 \(0\)

因此我们可以构造出不大于第一堆石子数的最大的\(a\)

第一种情况相当于\(z \& y \neq 0\),此时无解。

由于\(1,0\)的情况两者都必须为\(1\),因此我们可以初始化 \(a\)的值为 \(z\),然后从高位依次判断\(y\)的每一位,看看让\(a\)在该位为 \(1\)会不会大于第一石子数,不大于则可以为\(1\)

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;

int main(void) {
    ios::sync_with_stdio(false); 
    cin.tie(0); cout.tie(0);
    int n;
    cin >> n;
    vector<LL> a(n);
    for(auto &i : a){
        cin >> i;
    }
    LL x = a[0] + a[1];
    LL y = 0;
    for(int i = 2; i < n; ++ i)
        y = y ^ a[i];
    auto solve = [&](){
        if ((x - y < 0) || ((x - y) & 1))
            return a[0] + 1;
        x = (x - y) / 2;
        if ((x & y) != 0)
            return a[0] + 1;
        if (x > a[0])
            return a[0] + 1;
        LL ans = x;
        for(int i = 60; i >= 0; -- i){
            if (((y >> i) & 1) && ans + (1ll << i) <= a[0])
                ans += (1ll << i);
        }
        if (ans == 0)
            ans = a[0] + 1;
        return ans;
    };
    LL ans = solve();
    cout << a[0] - ans << '\n';

    return 0;
}



posted @ 2023-01-25 13:24  ~Lanly~  阅读(74)  评论(0编辑  收藏  举报