Codeforces Round #486 (Div. 3)

pantw

2018-06-04 23:20:41

Personal

emmm 第一次领着萌新们打cf。(OI队六一联欢晚会2333 本来觉得Div3应该会友好一点emmm 结果撞上一场披着Div3皮的Div2。(怒怼出题人 本来当天晚上想讲题结果发现时间不早( 于是就坑到了今天(2018 Jun. 6th)。 一放高考假肚子疼了半个晚上躺在床上动都不想动一下emmm 最后还是lijyya来催了催才开工。( 好的! 那么以下就是原创中文版题解咯。 ------------ # 解题报告 - ## A - ### 题意: 从$n$名学生中选$k$名参赛,每个人的能力值$a_i$不一样。给出$n$和$k$以及每个人的能力值$a_i$,要求判定能否按照要求选出这$k$名学生?要求输出方案。 $(1≤k≤n≤100, 1≤a_i≤100)$ - ### 思路: 我们观察到$a_i$的值域非常小,这说明我们可以用一个数组记录这个值我们是否选过。对每个值我们只能选1个人,也就是说有多少种能力值就能选多少人。 也就是说,我们从头往尾扫,有k种能力值时我们就停下来,这时已经满足条件。 如何输出方案:对每种能力值记录它第一次出现在谁身上。 其余实现细节详见代码。 - ### Code: ```cpp #include <cstdio> #define maxn 105 int a[maxn], cnt[maxn]; bool yes[maxn]; int main(){ int n, k; scanf("%d%d", &n, &k); for(int i = 1; i <= n; i++){ scanf("%d", a + i); if(k && !cnt[a[i]]++) yes[i] = true, --k; } if(!k) { puts("YES"); for(int i = 1; i <= n; i++) if(yes[i]) printf("%d ", i); } else puts("NO"); return 0; } ``` - ## B - ### 题意: 给$n$个字符串,求能否将其顺序重排,使得对任意一个串,排在它前面的都是它的子串。若能,输出方案。 $1≤n≤100$,字符串中字母均为小写,且串的长度$S_i$满足$1 \le S_i \le 100$。 - ### 思路: 我们可以发现: 1. 子串的长度一定小于等于原串的长度。 所以长度小的串一定要排在前面。 2. 对于长度相等的串,它们的内容必须相同。 所以我们只需要判定长度相等的串是否相同即可。 基于以上两点我们可以方便地利用string类来解决这个问题。 关于string类、string::find()函数的使用,请参阅:[string](http://zh.cppreference.com/w/cpp/string/basic_string)及[string::find()](http://zh.cppreference.com/w/cpp/string/basic_string/find) 其余实现细节详见代码。 - ### Code: ```cpp #include <string> #include <iostream> #include <cstdio> #include <algorithm> #define maxn 105 using namespace std; string s[maxn]; bool cmp(const string &a, const string &b) { return a.length() < b.length(); } int main(){ int n; cin >> n; for(int i = 1; i <= n; ++i) cin >> s[i]; sort(s + 1, s + n + 1, cmp); bool valid = true; for(int i = 2; i <= n; ++i) { if(s[i-1].length() == s[i].length() && s[i-1] != s[i]) valid = false; for(int j = 1; j < i; ++j) { if(s[i].find(s[j]) == s[i].npos) valid = false; } } puts(valid ? "YES" : "NO"); if(valid) for(int i = 1; i <= n; ++i) cout << s[i] << endl; return 0; } ``` - ## C - ### 题意: 给$k$个数组,每个数组长度$n_i$,其元素之和为$S_i$,求是否存在4个正整数$i,x,j,y$满足$i\neq j$且$S_i$减去第$i$个数组的第$x$个元素等于$S_j$减去第$j$个数组的第$y$个元素。存在则输出方案。 元素的绝对值在区间[-10000,10000]上。 $(2 \le k \le 2 \times 10^5, 1 \le n_i \le 2 \times 10^5, \sum_{i=1}^{k} n_i ≤ 2\times 10^5$ - ### 思路: 我们对每个元素枚举它所在的数组的元素和减去它所得到的值。 我们希望在发现第二个同样的值时终止程序,输出方案,从而解决这个问题。 因此我们需要一个能快速存储、查找值的数据结构。同时它还要维护值与位置信息之间的对应关系。 我们可以想到用STL容器中的map来解决这个问题。 先用struct定义位置信息这个类型。然后用map<int, 位置信息>这个类型的对象来存储信息。这样我们就完美地解决了这个问题。 其余实现细节详见代码。 - ### Code: ```cpp #include <cstdio> #include <vector> #include <map> #define maxn 200010 using namespace std; vector<int> a[maxn]; int n[maxn]; struct R{ int i, x; }; map<int, R> M; int main(){ int k, t; scanf("%d", &k); for(int i = 1; i <= k; i++){ scanf("%d", n + i); int sum = 0; for(int j = 1; j <= n[i]; ++j) scanf("%d", &t), a[i].push_back(t), sum += t; for(int j = 1; j <= n[i]; ++j) { R r; r.i = i, r.x = j; if(M.find(sum - a[i][j-1]) == M.end()) { M[sum - a[i][j-1]] = r; } else { R r_ = M[sum - a[i][j-1]]; if(r_.i != i) { puts("YES"); printf("%d %d\n%d %d\n", r_.i, r_.x, i, j); return 0; } } } } return puts("NO"), 0; } ``` - ## D - ### 题意: 数轴上有$n$个不重合的点,坐标分别为$x_1,x_2,...,x_n$。从这些点中选出若干个点组成集合$S$,使得$\forall x_i,x_j\in S,\exists k\in N,|x_i-x_j|=2^k.$ 求集合$S$最多能包含几个元素。输出方案。 - ### 思路: 经过缜密的思考我们可以发现,$S$不会包含超过3个元素。 证明: 若S包含3个以上的元素,取其最大的4个元素,从小到大设为$a,b,c,d$,则$d-c=2^i,c-b=2^j,d-b=2^k.$ 易证当且仅当$i=j=k-1$时,$d-c=2^i,c-b=2^j,d-b=2^k$同时成立。即$d-c=2^i,c-b=2^i=d-c$。 同理可证$b-a=c-b$。 $\therefore d-c=c-b=c-a=2^i$ $\therefore d-a=3\times 2^i \neq 2^k$,矛盾。 因此S中元素数不会超过3。 接下来我们只需要枚举答案即可。 答案可能的取值有:1,2,3。 对答案为1的情况:随便输出一个点。 对答案为2的情况:对每一个$a_i$,枚举$1\le k \le 31$,判断是否存在坐标为$a_i+2^k$的点。 对答案为3的情况:对每一个$a_i$,枚举$1\le k \le 31$,判断坐标为$a_i\pm 2^k$的点是否同时存在。 我们用一个set来存储所有点的坐标,这样可以快速查找某坐标对应的点是否存在。 其余实现细节详见代码。 - ### Code: ```cpp #include <cstdio> #include <set> #define maxn 200010 using namespace std; int a[maxn]; set<int> S; int main(){ int n; scanf("%d", &n); for(int i = 1; i <= n; i++) { scanf("%d", a + i); S.insert(a[i]); } for(int i = 1; i <= n; ++i) { for(int k = 0; k <= 30; ++k) { int x = a[i], d = 1 << k; if(S.find(x - d) != S.end() && S.find(x + d) != S.end()) { puts("3"); printf("%d %d %d", x - d, x, x + d); return 0; } } } for(int i = 1; i <= n; ++i) { for(int k = 0; k <= 30; ++k) { int x = a[i], d = 1 << k; if(S.find(x - d) != S.end()) { puts("2"); printf("%d %d", x - d, x); return 0; } } } puts("1"); printf("%d", a[1]); return 0; } ``` - ## E - ### 题意: 求能否将一个数经过合法交换相邻数字变成能被25整除的数。 - ### 思路: 这是一道公认的毒瘤题。 暴力枚举末两位数,贪心地移动数字。 细节较多,容易写错。 puts()、sscanf()、swap()以及strcpy()的用法请自行查阅cppreference。 其余实现细节详见代码。(其实本来可以不写这么长的emmm但是现场急着A题就没去管代码复用率) - ### Code: ```cpp #include <cstdio> #include <cstring> #include <algorithm> #define maxn 20 using namespace std; char s[maxn], t[maxn]; int cnt[maxn]; int main(){ int n, ans = 0x3f3f3f3f, i, j; long long d; scanf("%s", s); sscanf(s, "%I64d", &d); n = strlen(s); for(int i = 0; i < n; ++i) cnt[s[i] - '0']++; if(n <= 1) return puts("-1"), 0; if(n == 2) { if(d == 25 || d == 50 || d == 75) return puts("0"), 0; else if(d == 52 || d == 57) return puts("1"), 0; else return puts("-1"), 0; } if(cnt[0] >= 2) { int ta = 0; strcpy(t, s); for(j = n - 1; t[j] != '0'; --j); for(; j < n - 1; ++j) swap(t[j], t[j + 1]), ++ta; for(j = n - 2; t[j] != '0'; --j); for(; j < n - 2; ++j) swap(t[j], t[j + 1]), ++ta; if(ta < ans) ans = ta; } if(cnt[0] >= 1 && cnt[5] >= 1) { int ta = 0; strcpy(t, s); for(j = n - 1; t[j] != '0'; --j); for(; j < n - 1; ++j) swap(t[j], t[j + 1]), ++ta; for(j = n - 2; t[j] != '5'; --j); for(; j < n - 2; ++j) swap(t[j], t[j + 1]), ++ta; for(j = 0; t[j] == '0'; ++j) ++ta; if(j < n - 2 && ta < ans) ans = ta; } if(cnt[2] >= 1 && cnt[5] >= 1) { int ta = 0; strcpy(t, s); for(j = n - 1; t[j] != '5'; --j); for(; j < n - 1; ++j) swap(t[j], t[j + 1]), ++ta; for(j = n - 2; t[j] != '2'; --j); for(; j < n - 2; ++j) swap(t[j], t[j + 1]), ++ta; for(j = 0; t[j] == '0'; ++j) ++ta; if(j < n - 2 && ta < ans) ans = ta; } if(cnt[7] >= 1 && cnt[5] >= 1) { int ta = 0; strcpy(t, s); for(j = n - 1; t[j] != '5'; --j); for(; j < n - 1; ++j) swap(t[j], t[j + 1]), ++ta; for(j = n - 2; t[j] != '7'; --j); for(; j < n - 2; ++j) swap(t[j], t[j + 1]), ++ta; for(j = 0; t[j] == '0'; ++j) ++ta; if(j < n - 2 && ta < ans) ans = ta; } printf("%d", ans == 0x3f3f3f3f ? -1 : ans); return 0; } ``` - ## F - ### 题意: 暂请自行阅读题面。 - ### 思路: 一道DP题emmm 状态:$f[i][j]$表示走到i,拿着j点的伞时的最小疲劳度。 转移方程及其余实现细节详见代码。 - ### Code: ```cpp #include <cstdio> #include <cstring> #define maxn 2010 bool rain[maxn], um[maxn]; int w[maxn]; int f[maxn][maxn]; inline void cmin(int &p, int x) { if(x < p) p = x; } int main(){ memset(f, 0x3f, sizeof f); memset(w, 0x3f, sizeof w); int a, n, m, l, r; scanf("%d%d%d", &a, &n, &m); for(int i = 1; i <= n; ++i) { scanf("%d%d", &l, &r); ++l, ++r; for(int j = l; j < r; ++j) rain[j] = true; } for(int i = 1; i <= m; ++i) { scanf("%d%d", &l, &r); ++l; um[l] = true; cmin(w[l], r); } f[1][0] = w[0] = 0; for(int i = 1; i <= a + 1; ++i) { for(int j = 0; j <= a + 1; ++j) { if(um[i]) cmin(f[i][i], f[i][j]); // Switch to i if point i did have an umbrella cmin(f[i][0], f[i][j]); // Throw away the umbrella // Walk one unit distance if(rain[i]) { // If the interval is raining we could only walk with umbrella if(j) cmin(f[i + 1][j], f[i][j] + w[j]); // By carrying the umbrella with us, we'll be accumulating w[j] units of fatigue } else { cmin(f[i + 1][j], f[i][j] + w[j]); // We could carry the umbrella even if there isn't any rain. } } if(!rain[i]) cmin(f[i + 1][0], f[i][0]); cmin(f[i + 1][i], f[i][i] + w[i]); } int ans = 0x3f3f3f3f; for(int j = 0; j <= a + 1; ++j) cmin(ans, f[a + 1][j]); printf("%d", ans == 0x3f3f3f3f ? -1 : ans); return 0; } ```