1. 程式人生 > 遊戲攻略 >《原神攻略》謎境懸兵玩法與第一關寶箱點位分享

《原神攻略》謎境懸兵玩法與第一關寶箱點位分享

A - Luntik and Concerts B - Luntik and Subsequences C - Grandma Capa Knits a Scarf D - Vupsen, Pupsen and 0 E - Pchelyonok and Segments F1 - Korney Korneevich and XOR (easy version) F2 - Korney Korneevich and XOR (hard version)

比賽地址

A(水題)

題目連結

題目:
給出 \(a,b,c\) 三個數,保證均為正數,其中 \(a\) 權值為1, \(b\) 權值為\(2\)\(c\) 權值為3,將 \(a,b,c\) 分成兩堆,兩堆的權值和的差最小是多少

解析:
考慮取若干個 \(2\) ,則能取到 \([0,2b]\) 間所有的偶數,再加上若干個 \(1\) ,則能取到 \([0,a+2b]\) 所有數,對於 \([a+2b+1,a+2b+3c]\) ,可以由 \([0,a+2b]\) 中的某些數加若干個 \(3\) 獲得,因此若 \(2\mid a+2b+3c\),則一定可以得到 \(\frac{a+2b+3c}{2}\)

,反之一定會有最小差 \(1\)

#include <bits/stdc++.h>

int main() {
    int T;
    int a, b, c;
    scanf("%d", &T);
    while (T--) {
        scanf("%d%d%d", &a, &b, &c);
        printf("%d\n", (1ll * a + 2 * b + 3 * c) & 1);
    }
}

B(組合數學)

題目連結

題目:
給出陣列 \(a\),且 \(\sum_{i=1}^na_i=s\)

,找出有多少個子集滿足 \(\sum_{i\in subset}a_i=s-1\)

解析:
子集必須不包含集合中任意一個 \(1\),且可以包含任意個\(0\),所以答案即為\(cnt_1\times2^{cnt_0}\)

#include <bits/stdc++.h>

int main() {
    int T;
    int n, one, zero;
    std::vector<int> a;
    scanf("%d", &T);
    while (T--) {
        scanf("%d", &n);
        a.assign(n, 0);
        for (int i = 0;i < n;++i)
            scanf("%d", &a[i]);
        one = std::count_if(a.begin(), a.end(), [](int x) {return x == 1;});
        zero = std::count_if(a.begin(), a.end(), [](int x) {return x == 0;});
        printf("%lld\n", 1ll * one * (1ll << zero));
    }
}

C(雙指標+搜尋)

題目連結

題目:
一個字串,可以指定刪除某個特定字元任意次,求出刪除字元最小的次數,使其構成一個迴文串,如果無法得到則輸出 \(-1\)

解析:
進行搜尋,給出左右兩個指標 \(l,r\),如果\(str_l=str_r\),則繼續向中間搜尋,否則考慮刪除\(str_l\ or\ str_r\),統計成功得到迴文串結果的最小值

#include <bits/stdc++.h>

int main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(0);
    
    std::string str;
    int T, n,ans;
    std::cin >> T;
    while (T--) {
        std::cin >> n >> str;
        ans = 0x3f3f3f3f;
        std::function<void(int, int, char, int)> dfs = [&](int l, int r, char ch, int cur) {
            if (l >= r) {
                ans = std::min(ans, cur);
                return;
            }
            if (str[l] == str[r]) dfs(l + 1, r - 1, ch, cur);
            else {
                if (ch) {
                    if (str[l] == ch) dfs(l + 1, r, ch, cur + 1);
                    else if (str[r] == ch) dfs(l, r - 1, ch, cur + 1);
                    else return;
                }
                else {
                    dfs(l + 1, r, str[l], 1);
                    dfs(l, r - 1, str[r], 1);
                }
            }
        };
        dfs(0, n - 1, 0, 0);
        printf("%d\n", ans == 0x3f3f3f3f ? -1 : ans);
    }
}

D(構造)

題目連結
⭐⭐

題目:
給出一個無零陣列\(a\),找出另一個相同長度的無零陣列\(b\)使得,\(\sum_{i=1}^na_ib_i=0\),且要求\(\sum_{i=1}^nb_i<10^9\)

解析:
考慮\(n\)為偶數或者奇數

  • 偶數時,求出\(lcm(a_i,a_{i+1})\),指定好\(a_ib_i,a_{i+1}b_{i+1}\)的符號,對應\(|b_i|=\frac{lcm}{a_i},|b_{i+1}|=\frac{lcm}{a_{i+1}}\)
  • 奇數時,則考慮將{\(a_1,a_2,a_3\)}單獨拿出考慮,剩下部分仍按偶數情況處理,這時不能使用\(lcm\),三個數的\(\frac{lcm}{a_i}\)可能大於\(1e9\),考慮構造方式為\(b_1=a_2+a_3,b_2=a_1,b_3=a_1\)

另附 :
偶數時也可以使得 \(|b_i|=|a_{i+1}|,|b_{i+1}|=|a_i|\)\(lcm\),可以讓\(sum(b)\)更小一點,但會多花一些時間

#include <bits/stdc++.h>

using i64 = long long;
using std::abs;

int main() {
    std::function<i64(i64, i64)> gcd = [&](i64 a, i64 b) {
        return b ? gcd(b, a % b) : a;
    };
    int T, n;
    std::vector<i64> a;
    i64 lcm;
    scanf("%d", &T);
    while (T--) {
        scanf("%d", &n);
        a.assign(n, 0ll);
        for (int i = 0;i < n;++i)
            scanf("%lld", &a[i]);
        if (n & 1) {
            if (a[0] < 0) printf("-");
            printf("%lld ", abs(a[1])+abs(a[2]));
            if (a[1] > 0) printf("-");
            printf("%lld ", abs(a[0]));
            if (a[2] > 0) printf("-");
            printf("%lld ", abs(a[0]));
            for (int i = 3; i < n; i += 2) {
                lcm = abs(a[i]) * abs(a[i + 1]) / gcd(abs(a[i]), abs(a[i + 1]));
                if (a[i] < 0)  printf("-");
                printf("%lld ", lcm / abs(a[i]));
                if (a[i + 1] > 0)  printf("-");
                printf("%lld ", lcm / abs(a[i + 1]));
            }
        }
        else {
            for (int i = 0; i < n; i += 2) {
                lcm = abs(a[i]) * abs(a[i + 1]) / gcd(abs(a[i]), abs(a[i + 1]));
                if (a[i] < 0)  printf("-");
                printf("%lld ", lcm / abs(a[i]));
                if (a[i + 1] > 0)  printf("-");
                printf("%lld ", lcm / abs(a[i + 1]));
            }
        }
        printf("\n");
    }
}

E(dp)

題目連結
⭐⭐⭐

題目:
給出陣列\(a\),找出最小的\(k\),使得在\(a\)中有\(k\)個互不相交的區間 \([l_1,r_1],[l_2,r_2]\dots [l_k,r_k]\),並滿足

  • \(r_i-l_i+1=k-i+1\)
  • \(r_i<l_{i+1}\)
  • \(\sum_{j=l_i}^{r_i}a_j<\sum_{j=l_{i+1}}^{r_{i+1}}a_j\)

解析:
定義狀態 \(dp(i,j)\) 代表從下標 \(i\) 開始選取區間時,第一個長度為\(j\)的區間和的最大值,考慮選取 \(a_i\) 作為第一個區間中的一個元素時,此時必須要求 \(sum(i,i+j-1)<dp(i+j,j-1)\) ,才可以有貢獻 \(sum(i,i+j-1)\) ,或者不選取\(a_i\)時直接繼承 \(dp(i+1,j)\) 即可
答案統計\(dp(1,j)\)中被更新狀態過的 \(j\) 即可

#include <bits/stdc++.h>

using i64 = long long;

int main() {
    int T;
    int n, k;
    std::vector<i64> a, sum;
    std::vector<std::vector<i64>> dp;
    scanf("%d", &T);
    while (T--) {
        scanf("%d", &n);
        k = (-1 + std::sqrt(1 + 8 * n)) / 2;
        dp.assign(n+2, std::vector<i64>(k + 1, 0));
        for (int i = 2;i <= n + 1;++i)
            dp[i][0] = 0x3f3f3f3f;
        a.assign(n + 1, 0), sum.assign(n + 1, 0);
        for (int i = 1;i <= n;++i)
            scanf("%lld", &a[i]), sum[i] = sum[i - 1] + a[i];
        for (int i = n;i;--i)
            for (int j = 1;j <= k;++j) {
                dp[i][j] = dp[i + 1][j];
                if (i + j - 1 <= n && sum[i + j - 1] - sum[i - 1] < dp[i + j][j - 1])
                    dp[i][j] = std::max(dp[i][j], sum[i + j - 1] - sum[i - 1]);
            }
        for (int i = k;i;--i)
            if (dp[1][i]) {
                printf("%d\n", i);
                break;
            }
    }
}

F hard version(dp)

題目連結
⭐⭐⭐

題目:
給出陣列\(a\),找出其中嚴格遞增的子序列,其中所有元素的異或和可能是多少

解析:
定義狀態 \(dp(i,j)\) 是子序列最後一個數小於等於 \(i\) 時,異或和為 \(j\) ,子序列最後一個數下標的最小值
考慮 \(i\) 作為子序列中最後一個數,則可以從 \(dp(i-1,i\oplus j)\) 中繼承,但需要保證值為\(i\)的下標中存在一個下標大於\(dp(i-1,i\oplus j)\),這樣才可以構成一個子序列
答案在 \(dp(\max(a_i),j)\) 找到所有狀態被更新的\(j\)

#include <bits/stdc++.h>

int main() {
    constexpr int maxx = 8192, maxa = 5000;
    std::vector dp(maxa + 1, std::vector<int>(maxx, 0x3f3f3f3f));
    std::vector pos(maxa + 1, std::vector<int>());
    int n, t;
    scanf("%d", &n);
    for (int i = 0; i < n; ++i) {
        scanf("%d", &t);
        pos[t].push_back(i);
    }
    dp[0][0] = 0;
    for (int i = 1; i <= maxa; ++i) {
        for (int j = 0; j < maxx; ++j) {
            dp[i][j] = dp[i - 1][j];
            if (pos[i].empty()) continue;
            auto t = std::lower_bound(pos[i].begin(), pos[i].end(), dp[i - 1][i ^ j]);
            if (t != pos[i].end()) dp[i][j] = std::min(dp[i][j], *t);
        }
    }
    printf("%d\n", std::count_if(dp[maxa].begin(), dp[maxa].end(), [](int x) {return x != 0x3f3f3f3f; }));
    for (int i = 0; i < maxx; ++i) {
        if (dp[maxa][i] != 0x3f3f3f3f)
            printf("%d ", i);
    }
}
努力變成更好的自己把!