Codeforces Round #602 (Div. 2, based on Technocup 2020 Elimination Round 3) 解题报告(A~F)(E/F(hard)无)
Codeforces Round #602 (Div. 2, based on Technocup 2020 Elimination Round 3) 解题报告(A~F)(E/F(hard)无)
A:Math Problem
目标区间右端点一定是所有给定区间左端点的最大值。
目标区间左端点一定是所有给定区间右端点的最小值。
输出即可,注意负数。
#include<bits/stdc++.h> using namespace std; int T, n, a, b; int main() { cin >> T; while(T--) { int a = 0, b = 1e9; scanf("%d", &n); for(int i = 1, x, y; i <= n; i++) { scanf("%d%d", &x, &y); a = max(a, x); b = min(b, y); } cout << max(0, a - b) << endl; } return 0; }
B:Box?
贪心。
假设输入序列为\(a\)序列,答案序列为\(b\)序列。
那么如果\(a\)序列上升了,\(b\)序列自然就是\(a\)序列的值。
如果\(a\)序列不变,那么就从没有出现的数字中取最小的数字。
贪心过程中注意特判无解情况。
#include<bits/stdc++.h> using namespace std; const int maxn = 1e5 + 10; int T, n; int a[maxn]; int b[maxn]; void solve() { scanf("%d", &n); set<int> s; for(int i = 1; i <= n; i++) { scanf("%d", &a[i]); s.insert(i); } int mx = 0; for(int i = 1; i <= n; i++) { if(a[i] > mx) { if(s.count(a[i])) { b[i] = a[i]; s.erase(b[i]); mx = a[i]; } else{ puts("-1"); return; } } else if(a[i] == mx) { if(*s.begin() > mx) { puts("-1"); return; } b[i] = *s.begin(); s.erase(s.begin()); } } for(int i = 1; i <= n; i++) printf("%d ", b[i]); puts(""); } int main() { scanf("%d", &T); while(T--) solve(); return 0; }
C:Messy?
首先这题初看很难做,但其实分析一下就可以转换为一个构造题。
首先长度为\(n\)的序列经过\(n\)次的操作一定能变成任意的括号序列。
那么其实我们只需要把序列构造成满足有\(k\)个合法前缀的情况。
怎么构造呢,先加入\(k-1\)个\(()\),然后剩余的全部都加上\(((..))\)这样的。
打个比方:
- 比如说样例\(1\)的\(8\ 2\),就先构造\(()\),然后加上\(((()))\)。
- 样例\(2\)的\(10\ 3\),就构造\(()()\),然后加上\(((()))\)。
之后根据最终的序列构造操作。
#include<bits/stdc++.h> #define PII pair<int, int> using namespace std; const int maxn = 2000 + 10; int T, n, k; string s, ans; void swap_str(int x, int y) { if(x > y) swap(x, y); while(x < y) { swap(s[x], s[y]); x++, y--; } } void get_op() { vector<PII> op; for(int i = 0; i < (int)s.size(); i++) { if(s[i] != ans[i]) { for(int j = i+1; j < (int)s.size(); j++) { if(s[i] != s[j]) { swap_str(i, j); op.push_back({i+1, j+1}); break; } } } } cout << op.size() << endl; for(auto x : op) printf("%d %d\n", x.first, x.second); } int main() { scanf("%d", &T); while(T--) { scanf("%d%d", &n, &k); cin >> s; ans = ""; for(int i = 1; i <= k - 1; i++) ans += '(', ans += ')'; int tmp = n - (k-1) * 2; for(int i = 1; i <= tmp / 2; i++) ans += '('; for(int i = 1; i <= tmp / 2; i++) ans += ')'; //cout << ans << endl; get_op(); } return 0; }
D:Optimal Subsquences(Hard Version)
主席树+贪心。
我们需要满足子序列和最大,所以这\(k\)个数,一定是我们将序列从大到小排序后的前\(k\)个数字。
于是乎我要选择的\(k\)个数确定了。
同时我们需要保证字典序最小。
设\(num\)为第\(k\)大的元素,要想让字典序最小,那么我们需要做的就是让所有\(num\)出现的位置尽可能靠前,也就是尽可能的挑选\(idx\)小的元素。
所以我们记录序列\(val\)时同时记录序列的\(id\),之后按照\(val\)排序,\(val\)相等的两项让\(id\)小的在前。
之后用主席树维护\(id\)。
对于每次询问,找到对应的树与空树进行查询第\(pos\)大的数在原序列的位置,找到这个数即为答案。
#include<bits/stdc++.h> using namespace std; const int maxn = 2e5 + 10; int n, m, mp[maxn]; //mp(i)表示第i次询问对应的那个版本的线段树 struct Arr { int v, id; }a[maxn]; int b[maxn]; bool cmp(Arr a, Arr b) { if(a.v == b.v) return a.id < b.id; return a.v > b.v; } struct Node{ int k, pos, id; }q[maxn]; bool cmp1(Node a, Node b){ return a.k < b.k; } int ans[maxn]; //-----------主席树部分 int sum[maxn<<5], ls[maxn<<5], rs[maxn<<5]; int rt[maxn<<5], tot; int build(int l, int r) { int root = ++tot; if(l == r) return root; int mid = (l + r) >> 1; ls[root] = build(l, mid); rs[root] = build(mid+1, r); return root; } int update(int pre, int l, int r, int k) { int root = ++tot; ls[root] = ls[pre], rs[root] = rs[pre], sum[root] = sum[pre] + 1; if(l == r) return root; int mid = (l + r) >> 1; if(k <= mid) ls[root] = update(ls[pre], l, mid, k); else rs[root] = update(rs[pre], mid+1, r, k); return root; } int query(int u, int v, int l, int r, int k) { if(l == r) return l; int x = sum[ls[v]] - sum[ls[u]]; int mid = (l + r) >> 1; if(k <= x) return query(ls[u], ls[v], l, mid, k); else return query(rs[u], rs[v], mid+1, r, k - x); } //------------------- void init() { scanf("%d", &n); for(int i = 1; i <= n; i++) { scanf("%d", &a[i].v); a[i].id = i; b[i] = a[i].v; } sort(a+1, a+1+n, cmp); scanf("%d", &m); for(int i = 1, x, y; i <= m; i++){ scanf("%d%d", &x, &y); q[i] = {x, y, i}; } sort(q+1, q+1+m, cmp1); } int main() { init(); rt[0] = build(1, n); int p = 1; for(int i = 1; i <= m; i++) { if(q[i].k < p) { mp[i] = mp[i-1]; //k相等的话用的是同一棵线段树 continue; } //插入对应的id for(int j = p; j <= q[i].k; j++) rt[j] = update(rt[j-1], 1, n, a[j].id); mp[i] = rt[q[i].k]; p = q[i].k + 1; } for(int i = 1; i <= m; i++) { int t = query(rt[0], mp[i], 1, n, q[i].pos); ans[q[i].id] = b[t]; } for(int i = 1; i <= m; i++) printf("%d\n", ans[i]); return 0; }
F:Wrong Answer on test 233(Easy/Hard Version)
题意:
- 给定\(n\)道题,每道题有\(k\)个选项,有且仅有一个正确答案。
- 现在给你犯傻了,把第\(1\)题涂到了第\(2\)题,第\(2\)题涂到了第\(3\)题\(,...,\)第\(n\)题涂到了第\(1\)题。
- 给你原先\(n\)道题的答案,问你有多少种答案的可能,让你错误提交之后比你正确提交的分数高。
数据范围:
- \(Easy Version\ 1\leq n\leq 2000\)。
- \(Hard Version\ 1\leq n\leq 10^5\)。
思路(\(Easy\ Version\)):
\(dp\)。
设\(f(i,j)\)表示考虑到了第\(i\)题,交换后比交换前多得\(j\)分的结果数。
如果\(h(i)==h(i+1)\)的话,\(f(i,j)=f(i-1,j)\),因为这时候交换并不会增加结果。
其他情况,\(f(i,j)=f(i-1,j+1)+f(i-1,j-1)+(k-2)*f(i-1,j)\)
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int maxn = 2e3 + 10; const int mod = 998244353; ll n, k, h[maxn], f[maxn][maxn<<1]; int main() { ios::sync_with_stdio(false); cin.tie(0); cin >> n >> k; for(int i = 1; i <= n; i++) cin >> h[i]; if(k == 1) { puts("0"); return 0; } f[0][2001] = 1; for(int i = 1; i <= n; i++) for(int j = 1; j < maxn<<1; j++) { if(h[i] == h[i%n+1]) f[i][j] = (f[i-1][j]*k)%mod; else f[i][j] = (f[i-1][j-1]+f[i-1][j+1]+f[i-1][j]*(k-2))%mod; } ll ans = 0; for(int i = 1; i <= n; i++) ans = (ans + f[n][2001+i]) % mod; cout << ans << endl; return 0; }