《原神攻略》謎境懸兵玩法與第一關寶箱點位分享
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}\)
#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\)
解析:
子集必須不包含集合中任意一個 \(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);
}
}
努力變成更好的自己把!