Codeforces Round #710 (Div. 3) A B C D E
第一次过五道题目,但是起手慢了,相对于其他选手的优势也很小.最后和平时的排名百分比没什么区别.
E题的通过人数是三千,到了F题只剩500形成断崖,所以想突破一道百人题对我还是很难.
码量是有一点的(因为平时div2太弱最多写到三题),基本上是在面向AC做题,使用各种基本技巧就可以了,处理起来需要稍加思考.
做完E题差不多只有十分钟了,这个手速肯定欠练.
早上一看发现E被hack了,持续掉分.= =
A
草稿纸上面计算一下在Colum模式下数字x所处的第i行,第j列,然后计算出在rows模式下第i行,第j列的数字即可.
主要用到取模和向上取整的整除.
我花了接近20分钟,打草稿的效率太低了,一直出错.
#include <algorithm> #include <cstdio> #include <cstring> #include <iostream> using namespace std; long long n, m, x; void solve(){ scanf("%lld%lld%lld", &n, &m, &x); long long i = x % n; long long j = (x - 1) / n + 1; if(!i) i = n; if(!j) j = m; printf("%lld\n", (i - 1) * m + j); } int main(){ int t; scanf("%d", &t); while(t--) solve(); return 0; }
B
数据保证有解.
按照题意模拟即可,先将第一个*改为x,之后维护上一个x的位置last,检测到 . 则跳过,检测到*且位置距离last大于k时,向前查找一个*满足其位置与last相差不大于k,将其改为x并更新last.
最后检查一下末端是否满足最后一个*改为了x.
#include <algorithm> #include <cstdio> #include <cstring> #include <iostream> #include <string> using namespace std; int n, k; string s; void solve() { cin >> n >> k >> s; bool hd = false; int last = 0, ans = 0; for (int i = 0; i < s.size(); i++) { if(s[i] == '.') continue; if(!hd){ s[i] = 'x'; last = i; hd = true; ans++; continue; } while(i - last > k){ int p = i; while(p - last > k || s[p] != '*') p--; last = p; s[p] = 'x'; ans++; } } for(int i = s.size() - 1; i >= 0; i--) if(s[i] == 'x') break; else if(s[i] == '*'){ ans++; break; } printf("%d\n", ans); } int main() { int t; scanf("%d", &t); while (t--) solve(); return 0; }
C
数据范围惊人地小,那么可以O(N2)对比,找到两字符串能匹配的最大长度,然后让其保留此长度的字符串并删除其余字符,需要的总操作数显然是(a的长度)+(b的长度)-2*(匹配的最长字符串的长度).
#include <algorithm> #include <cstdio> #include <cstring> #include <iostream> #include <string> using namespace std; string a, b; void solve() { cin >> a >> b; int big = 0; for (int i = 0; i < a.size(); i++) { for (int j = 0; j < b.size(); j++) { int ct = 0; int p1 = i, p2 = j; while (p1 < a.size() && p2 < b.size() && a[p1] == b[p2]) { ct++; p1++; p2++; } big = max(big, ct); } } printf("%d\n", a.size() + b.size() - 2 * big); } int main() { int t; scanf("%d", &t); while (t--) solve(); return 0; }
D
应该是前五题里最麻烦的吧.
我维护了一个数组rkct[i],其值为rank为i的数字(也就是离散化)出现的次数.数组的顺序是无关紧要的,可以舍弃这个信息.
也不需要关系rank为i的数字是谁,所以干脆再把rkct数组也升序排列,看起来更好操作.
想到过几种方法都没找到出路:
让rkct从两端相互抵消,不能处理112233,但可以处理11223333.
让rkct不停从左到右,相邻项抵消,可以处理112233,但不能处理11223333.
发现这两种方法分别解决了一个问题,于是有所启发:
①从首个数字不停地顺序,循环取走,直至下一个要取的数字会与上一个相同.
如11223333依次取出123123,剩下33.
会发现这时剩下的数字只会是出现次数最多的数字.
②将剩下的数字插入到取出的序列.
即将两个3插入到先前得到的123123中,这很容易做到,可以计算出可插入点(用0表示)010231023有三个.
最后仍然可能有数字剩下,这时就需要直接统计到答案里了.
注意,由于是两两消除,数组元素数量却会出现奇数情况,这只需要在最后处理答案时进行考虑.
#include <algorithm> #include <cstdio> #include <cstring> #include <iostream> #include <vector> using namespace std; int n, a[200010], rkct[200010]; vector<int> v; void solve(){ scanf("%d", &n); for(int i = 1; i <= n; i++) scanf("%d", a + i), rkct[i] = 0; sort(a + 1, a + n + 1); v.clear(); int cur = a[1], ind = 1; for(int i = 1; i <= n; i++){ if(a[i] == cur) rkct[ind]++; else cur = a[i], rkct[++ind]++; } if(ind == 1) {printf("%d\n", rkct[1]); return;} sort(rkct + 1, rkct + ind + 1); int l = 1; while(rkct[ind - 1]){ for(int i = l; i <= ind; i++){ rkct[i]--; v.push_back(i); } while(!rkct[l]) l++; } int spa = 0; for(int i = 0; i < v.size(); i++) if(v[i] != ind && (i == 0 || v[i - 1] != ind)) spa++; if(rkct[ind] <= spa){ printf("%d\n", (v.size() + rkct[ind]) % 2); }else{ int ans = (v.size() + spa) % 2; printf("%d\n", ans + rkct[ind] - spa); } // for(int i = 1; i <= ind; i++) printf("_%d ", rkct[i]); // puts(""); } int main(){ // freopen("in.txt", "r", stdin); int t; scanf("%d", &t); while(t--) solve(); return 0; }
E
对于数据给定的序列,若q[i]!=q[i-1],说明位置i一定为q[i].
依次可以确定出所有固定的点,剩下的未使用的点构成一个集合.
想要得到最小字典序很简单,从这个集合中不停取走最小者安排到空缺的位置即可.
别看这段↓
而想要得到最大字典序,对于未确定的位置i,插入的值范围为[1,q[i]-1],从q[i]-1一直递减,检查到第一个未使用值时将其插入并标记为已使用即可.
这个过程花费的时间复杂度我没有细算,但是TLE了.
加了了记忆化,数组big[x]表示小于x且可能 未使用的最大值,每当发现某个q[i]=x的点插入了值p,就将big[x]更新为p-1.
由于只是可能未使用,还是要不停向下检查直到找到确实未使用的值.
#include <algorithm> #include <cstdio> #include <cstring> #include <iostream> using namespace std; int n, q[200010], ans[200010]; int big[200010]; bool used[200010], tused[200010]; void solve() { memset(used, 0, sizeof(used)); scanf("%d", &n); for (int i = 1; i <= n; i++) { scanf("%d", q + i); big[q[i]] = q[i] - 1; ans[i] = 0; if (q[i] != q[i - 1]) { ans[i] = q[i]; used[q[i]] = true; } } int p = 1; memcpy(tused, used, sizeof(used)); for (int i = 1; i <= n; i++) { if (ans[i]) printf("%d ", ans[i]); else { while (tused[p]) p++; tused[p] = true; printf("%d ", p); } } puts(""); for(int i = 1; i <= n; i++){ if(ans[i]) printf("%d ", ans[i]); else{ int p = big[q[i]]; while(used[p]) p--; used[p] = true; big[q[i]] = p - 1; printf("%d ", p); } } puts(""); } int main() { int t; scanf("%d", &t); while (t--) solve(); return 0; }
醒醒,这个复杂度仍然是O(N2),你被hack了.
正确的求最大字典序方法是二分找值插入.
使用set,这个数据结构可以按值删除元素,也可以二分查找.
#include <algorithm> #include <cstdio> #include <cstring> #include <iostream> #include <set> using namespace std; int t, n; int a[200010]; set<int> s; void solve() { scanf("%d", &n); for (int i = 1; i <= n; i++) scanf("%d", &a[i]), s.insert(i); for (int i = 1; i <= n; i++) { if (a[i] != a[i - 1]) printf("%d ", a[i]), s.erase(a[i]); else printf("%d ", *s.begin()), s.erase(s.begin()); } puts(""); for (int i = 1; i <= n; i++) s.insert(i); for (int i = 1; i <= n; i++) { if (a[i] != a[i - 1]) printf("%d ", a[i]), s.erase(a[i]); else { auto it = prev(s.lower_bound(a[i])); printf("%d ", *it); s.erase(it); } } puts(""); } int main() { int t; scanf("%d", &t); while (t--) solve(); return 0; }