比赛链接:
https://vjudge.net/contest/507736
B - Boss Rush
题意:
有 \(n\) 个技能,第 \(i\) 个技能使用完后的 \(t_i\) 时间内不能使用其他技能,该技能会在 \(len_i\) 的时间中,每秒造成 \(d[i][j]\) 点伤害 \((1 <= j <= len_i)\),boss 有 \(H\) 滴血,问最短多少时间能杀死 boss。
思路:
先二分时间,然后通过记忆化搜索 + 状态压缩判断指定时间内是否能造成相对应的伤害。
代码:
#include <bits/stdc++.h> using namespace std; #define LL long long const int N = 20, M = 1e5 + 10, K = 3e5 + 10; LL n, H, t[N], len[N], d[N][M], pre[N][M], dp[K]; bool check(LL x){ for (int i = 0; i < (1 << n); i ++ ){ dp[i] = -1; } function<LL(LL)> dfs = [&](LL state){ if ( ~ dp[state]) return dp[state]; LL ans = 0, sumt = 0; for (int i = 0; i < n; i ++ ){ if (state >> i & 1){ sumt += t[i + 1]; //已经使用的技能的冷却时间 } } if (sumt > x) return dp[state] = 0; for (int i = 0; i < n; i ++ ){ if ( ! (state >> i & 1)){ //使用新的技能 ans = max(ans, pre[i + 1][min(len[i + 1], x - sumt)] + dfs(state | (1 << i))); } } return dp[state] = ans; }; return dfs(0) >= H; } void solve(){ cin >> n >> H; LL mx = 0; for (int i = 1; i <= n; i ++ ){ cin >> t[i] >> len[i]; mx += t[i] + len[i]; for (int j = 1; j <= len[i]; j ++ ) cin >> d[i][j]; for (int j = 1; j <= len[i]; j ++ ) pre[i][j] = pre[i][j - 1] + d[i][j]; } LL L = 0, R = mx + 1; while(L < R){ LL mid = (L + R) >> 1; if (check(mid)){ R = mid; } else{ L = mid + 1; } } if (L > mx) cout << "-1\n"; else cout << L - 1 << "\n"; } int main(){ ios::sync_with_stdio(false);cin.tie(0); LL T = 1; cin >> T; while(T -- ){ solve(); } return 0; }
C - Cyber Language
题意:
给定一行句子,输出每个单词的首字母的大写。
思路:
签到,按题意操作。
代码:
#include <bits/stdc++.h> using namespace std; #define LL long long char s[100]; void solve(){ gets(s); for (int i = 0; i < strlen(s); i ++ ) if (i == 0 || s[i - 1] == ' ') cout << (char)(s[i] - 'a' + 'A'); cout << "\n"; } int main(){ LL T = 1; cin >> T; getchar(); //把换行读取了 while(T -- ){ solve(); } return 0; }
I - Package Delivery
题意:
快递站有 \(n\) 个包裹,每个包裹从第 \(l\) 天存放到第 \(r\) 天,每次去快递站可以拿 \(k\) 个包裹,问最少去快递站几次能拿到所有包裹。
思路:
对于第 \(i\) 个快递,它的时间为 \(l_i\) 到 \(r_i\)。
按照贪心的策略,肯定拿 \(r_i\) 最小的那个快递,再从所有 \(l_j <= r_i <= r_j\) 的快递中选 \(r_j\) 最小的 \(k\) 个拿走。
代码:
#include <bits/stdc++.h> using namespace std; #define LL long long void solve(){ LL n, k; cin >> n >> k; vector < pair<LL, pair<LL, LL> > > st(n); vector < pair<LL, LL> > ed(n); for (int i = 0; i < n; i ++ ){ cin >> st[i].first >> ed[i].first; st[i].second = {ed[i].first, i}; ed[i].second = i; } sort(st.begin(), st.end()); sort(ed.begin(), ed.end()); priority_queue < pair<LL, LL>, vector< pair<LL, LL> >, greater< pair<LL, LL> > > q; vector <bool> used(n, false); LL ans = 0; for (int i = 0, j = 0; i < n; i ++ ){ if (used[ed[i].second]) continue; while(j < n && st[j].first <= ed[i].first){ q.push(st[j].second); j ++ ; } ans ++ ; for (int t = 0; t < k; t ++ ){ if (q.empty()) break; used[q.top().second] = true; q.pop(); } } cout << ans << "\n"; } int main(){ ios::sync_with_stdio(false);cin.tie(0); LL T = 1; cin >> T; while(T -- ){ solve(); } return 0; }
K - Taxi
题意:
有 \(n\) 个小镇,第 \(i\) 个小镇的位置为 \((x_i,y_i)\),费用为 \(w_i\),对于一个点 \((x^{'}, y^{'})\),它到第 \(i\) 个小镇的费用为 \(min(w_i, \lvert x^{'} - x_i \rvert + \lvert y^{'} - y_i \rvert)\),现有 q 次询问,每次告诉一个点 \((x^{'}, y^{'})\),问小镇到这个点的费用最大为多少?。
思路:
\(max(\lvert x^{'} - x_i \rvert, \lvert y^{'} - y_i \rvert)\)
= \(max(max(x^{'} - x_i, - x^{'} + x_i), max(y^{'} - y_i, - y^{'} + y_i))\)
= max{\((x^{'} + y^{'}) + (- x_i - y_i), (x^{'} - y^{'}) + (- x_i + y_i), (- x^{'} + y^{'}) + (x_i - y_i), (- x^{'} - y^{'}) + (x_i + y_i)\)}
所以只需要预处理出所有城镇坐标的四个属性,就可以通过 O(1) 的方法计算曼哈顿距离的答案。
而 \(w\) 则没办法,所以对 \(w\) 进行排序,使其单调,那么就可以通过二分取选择 \(w\)。
代码:
#include <bits/stdc++.h> using namespace std; #define LL long long const LL N = 1e5 + 10, INF = 1e18; struct node{ LL x, y, w; bool operator < (const node & x) const{ return w < x.w; } }point[N]; LL a[N], b[N], c[N], d[N], ans; void solve(){ LL n, q; cin >> n >> q; for (int i = 1; i <= n; i ++ ) cin >> point[i].x >> point[i].y >> point[i].w; sort(point + 1, point + n + 1); a[n + 1] = b[n + 1] = c[n + 1] = d[n + 1] = -INF; for (int i = n; i >= 1; i -- ){ a[i] = max(a[i + 1], point[i].x + point[i].y); b[i] = max(b[i + 1], point[i].x - point[i].y); c[i] = max(c[i + 1], - point[i].x + point[i].y); d[i] = max(d[i + 1], - point[i].x - point[i].y); } while(q -- ){ LL x, y; cin >> x >> y; ans = 0; LL L = 1, R = n; function <bool(LL)> check = [&](LL k){ LL mx = a[k] - x - y; mx = max(mx, b[k] - x + y); mx = max(mx, c[k] + x - y); mx = max(mx, d[k] + x + y); ans = max(ans, min(point[k].w, mx)); return point[k].w < mx; }; while(L < R){ LL mid = (L + R) >> 1; if (check(mid)){ L = mid + 1; } else{ R = mid; } } check(L); cout << ans << "\n"; } } int main(){ ios::sync_with_stdio(false);cin.tie(0); LL T = 1; cin >> T; while(T -- ){ solve(); } return 0; }
L - Two Permutations
题意:
给定两个序列 \(p\) 和 \(q\),每次重构从其中一个序列中取出首位元素,然后加入序列 \(r\) 的后面,直到两个序列所有元素被取完结束,现在给定序列 \(s\),问有多少种取法可以让 \(r = s\)。
思路:
因为每次只从 \(p\) 或者 \(q\) 中取出元素,所以可以定义 \(dp[i][0]\) 表示 \(r\) 序列的第 \(i\) 位是从 \(p\) 序列中取出来的方案数,\(dp[i][1]\) 表示 \(r\) 序列的第 \(i\) 位是从 \(q\) 序列中取出来的方案数。
如果该元素本来就是从本序列中取出,即 \(s[i - 1]\) 和 \(s[i]\) 是从同一个序列中取出的,那么它们位置的坐标差为 1。
如果是从不同序列中取出的,因为之前总共取了 \(i\) 个元素,那么两个序列中的位置之和一定等于 \(i\)。
所以在动态规划之前,先记录两个序列中每个元素的位置即可。
代码:
#include <bits/stdc++.h> using namespace std; #define LL long long const int mod = 998244353; void solve(){ LL n; cin >> n; vector <LL> p(n + 1), q(n + 1), id1(n + 1), id2(n + 1), s(2 * n + 1); for (int i = 1; i <= n; i ++ ){ cin >> p[i]; id1[p[i]] = i; } for (int i = 1; i <= n; i ++ ){ cin >> q[i]; id2[q[i]] = i; } for (int i = 1; i <= 2 * n; i ++ ) cin >> s[i]; vector < vector<LL> > dp(2 * n + 1, vector<LL>(2)); dp[0][0] = 1; LL ans = 0; for (int i = 1; i <= 2 * n; i ++ ){ LL pre = s[i - 1], now = s[i]; if (id1[now] == id1[pre] + 1) dp[i][0] = (dp[i][0] + dp[i - 1][0]) % mod; if (id1[now] + id2[pre] == i) dp[i][0] = (dp[i][0] + dp[i - 1][1]) % mod; if (id2[now] == id2[pre] + 1) dp[i][1] = (dp[i][1] + dp[i - 1][1]) % mod; if (id2[now] + id1[pre] == i) dp[i][1] = (dp[i][1] + dp[i - 1][0]) % mod; } cout << (dp[2 * n][0] + dp[2 * n][1]) % mod << "\n"; } int main(){ ios::sync_with_stdio(false);cin.tie(0); LL T = 1; cin >> T; while(T -- ){ solve(); } return 0; }