Codeforces Round #681 (Div. 2, based on VK Cup 2019-2020 - Final) [A - F]
题目来源
https://codeforces.ml/contest/1443
A. Kids Seating
在 [1, 4n]这个区间内寻找n个数字,使得这n个数两两不互质,两两不互相可以整除。
思路分析
从2n+2开始,每隔2取一个数字即可。
#include <bits/stdc++.h> using namespace std; const int maxn = 1e5 + 7; int a[maxn]; vector <int> ans; bool vis[maxn]; int main(){ int t; scanf("%d", &t); while (t --){ int n; scanf("%d", &n); int tmp = 4 * n; for (int i=1; i<=n; i++){ printf("%d ", tmp); tmp -= 2; } printf("\n"); } return 0; }
B. Saving the City
给一个数字序列。有两种操作,将某一位置上的'0'变成'1',这一步需要$b$费用;第二种操作是将一段连续的‘1’变成'0',这一步需要a费用。问最后将所有的数组都变成'0'的最小费用。
思路分析
统计两段‘1’序列之间的‘0’的个数p,比较p * b 和 a大小。即对于一段0序列,判断与前后一起消去以及前后两段1序列各自消去的费用的最小值。
#include <bits/stdc++.h> using namespace std; const int maxn = 1e5 + 7; char ch[maxn]; int main(){ int t; scanf("%d", &t); while (t --){ int a, b; scanf("%d%d", &a, &b); int ans = 0; bool flag = false; scanf("%s", ch); int ln = (int)strlen(ch); int num = 0; for (int i=0; i<ln; i++){ if (ch[i] == '1'){ if (!flag) ans += a; flag = true; ans += min(a, b * num); num = 0; }else if (flag) num ++; } printf("%d\n", ans); } return 0; }
C. The Delivery Dilemma
有n家餐厅,有两种派送方式,第一种是他配送,需要a[i]时间;第二种是自己去取,需要b[i]时间。若多家餐厅选择方式二进行派送,则需要自己一家一家取。问最少的时间费用是多少。
思路分析
可以想到的是,答案的取决于最大的派送时间,以及自己取的时间和。根据商家派送时间进行排序,然后贪心枚举每一次两个时间即可。
#include <bits/stdc++.h> #define int long long #define INF 0x3f3f3f3f using namespace std; const int maxn = 2e5 + 7; //int a[maxn], b[maxn]; struct node{ int a, b; }; node tt[maxn]; bool cmp(const node& u, const node& v){ if (u.a != v.a) return u.a > v.a; else return u.b > v.b; } signed main(){ int t; scanf("%lld", &t); while (t --){ int n; scanf("%lld", &n); for (int i=1; i<=n; i++) scanf("%lld", &tt[i].a); for (int i=1; i<=n; i++) scanf("%lld", &tt[i].b); sort(tt+1, tt+1+n, cmp); int mmax = tt[1].a; int ans = 0, res = INF; for (int i=1; i<=n; i++){ mmax = tt[i].a; // cout << res << " " << mmax << " " << ans << endl; res = min(res, max(mmax, ans)); ans += tt[i].b; } res = min(res, ans); printf("%lld\n", res); } return 0; }
D. Extreme Subtraction
给定一个数字序列,每一次可以从头选择一个连续区间进行-1, 也可以从尾部选择一个连续区间进行-1,问最后能否在多次操作之后,使得整个数字序列变成0。
思路分析
给区间进行一个增加减少的操作,可以联想到差分的思想。然后按照差分的思想就能找到最终的解法。
#include <bits/stdc++.h> #define int long long #define INF 0x3f3f3f3f using namespace std; const int maxn = 2e5 + 7; int a[maxn]; int p[maxn]; signed main(){ int t; scanf("%lld", &t); while (t --){ int n; scanf("%lld", &n); for (int i=1; i<=n; i++) scanf("%lld", &a[i]); for (int i=1; i<=n; i++) p[i] = a[i] - a[i-1]; int ans = 0; for (int i=2; i<=n; i++) if (p[i] < 0) a[1] += p[i]; if (a[1] >= 0) printf("YES\n"); else printf("NO\n"); } return 0; }
E. Long Permutation
给定一个全排列的长度n,表示为1到n的一个全排列。有两种操作。第一种操作是询问此时排列中,[l, r]这个区间的区间和为多少。另一种操作为将此时的全排列向后递增x次。
第二种的操作具体可以表现为,当n=3时,第一组全排列为 1 2 3 。 第二组为 1 3 2。依次类推。
思路分析
首先区间和可以使用线段树去实现。而对于第二种操作,根据数据范围可以想到的是他最多只会改变最后的14个数字。然后就可以根据逆康拓展开,求出此时的全排列,然后逐一的进行单点修改。
#include <iostream> #include <queue> #include <algorithm> #include <cstring> #include <cstdio> #include <vector> #include <cmath> #include <map> #define int long long using namespace std; const int maxn = 2e5 + 7; int a[maxn]; int frac[maxn], tree[maxn << 2]; void init(){ frac[0] = 1; int p = 0; for (int i=1; i<maxn; i++){ frac[i] = frac[i-1] * i; p = i; if (frac[i] > 2e10) break; } // cout << "P: " << p << endl; } void build(int p, int l, int r){ if (l == r){ tree[p] = a[l]; return; } int mid = l + r >> 1; build(p << 1, l, mid); build(p << 1 | 1, mid + 1, r); tree[p] = tree[p << 1] + tree[p << 1 | 1]; } int ask(int p, int l, int r, int L, int R){ if (L <= l && r <= R) return tree[p]; int mid = l + r >> 1; int ans = 0; if (mid >= L) ans += ask(p << 1, l, mid, L, R); if (mid < R) ans += ask(p << 1 | 1, mid+1, r, L, R); return ans; } void update(int p, int l, int r, int x, int y){ if (l == r){ tree[p] = y; return; } int mid = l + r >> 1; if (mid >= x) update(p << 1, l, mid, x, y); else if (mid < x) update(p << 1 | 1, mid + 1, r, x, y); tree[p] = tree[p << 1] + tree[p << 1 | 1]; } void decantor(int x, int n, int m) { vector<int> v; vector<int> _a; for(int i=1;i<=n;i++) v.push_back(i); for(int i=n;i>=1;i--) { int r = x % frac[i-1]; int t = x / frac[i-1]; x = r; sort(v.begin(),v.end()); _a.push_back(v[t]); v.erase(v.begin()+t); } // int ln = (int)_a.size(); int p = m - n + 1; for (int i=p; i<=m; i++){ a[i] = _a[i - p]; update(1, 1, m, i, _a[i-p] + p - 1); } } int num = 0; signed main(){ init(); int n, q; scanf("%lld%lld", &n, &q); for (int i=1; i<=n; i++) a[i] = i; build(1, 1, n); while (q --){ int op; scanf("%lld", &op); if (op == 1){ int l, r; scanf("%lld%lld", &l, &r); printf("%lld\n", ask(1, 1, n, l, r)); }else{ int x; scanf("%lld", &x); num += x; if (n >= 15) decantor(num, 15, n); else decantor(num, n, n); } } return 0; }
F. Identify the Operations
给定一个长度为n的a数组,其中所有数字是从1到n,各不相同。再给定一个b数组,表示需要筛选出来的数组。每一次操作,是在a数组中寻找一个位置p,将其两边的数字中的某一个数字筛选出来,并将p号位置上的数字删去。问有多少种操作排列组合能够最终筛选最终答案。
注:a, b数组中的数都各不相同。
思路分析
对于选出一个数字放入到最终的答案队列中,需要将这个数字两边的某一个数字删去。而我们不能删去b数组后面的数。
最终答案为过程中,各个数字筛选出来的结果数的乘积。而不能删选的结果数字为后续需要被筛选出来的那些数字。而对于被删掉的数字,其实不需要标记,因为当一个数字删除之后,其前后的位置会重新顶上来,这个位置也就被重新激活。
#include <bits/stdc++.h> #define int long long using namespace std; const int maxn = 2e5 + 7; const int mod = 998244353; int a[maxn], b[maxn], p[maxn]; bool vis[maxn]; signed main(){ int t; scanf("%lld", &t); while (t --){ int n, k; scanf("%lld%lld", &n, &k); for (int i=1; i<=n; i++) vis[i] = false; vis[0] = true; vis[n+1] = true; for (int i=1; i<=n; i++){ scanf("%lld", &a[i]); p[a[i]] = i; } for (int i=1; i<=k; i++){ scanf("%lld", &b[i]); vis[p[b[i]]] = true; } int ans = 1; for (int i=1; i<=k; i++){ int tmp = 0; if (!vis[p[b[i]] + 1]) tmp ++; if (!vis[p[b[i]] - 1]) tmp ++; vis[p[b[i]]] = false; ans = ans * tmp % mod; } printf("%lld\n", ans); } return 0; }