[比赛链接]
https://codeforces.com/contest/1029
[比赛经历]
本想靠DIV3上点分,没想到时差没有倒过来,最后三题收场,第二天SYSTEM TEST的时候D题还被卡常了,最后只加了1rating,算是一场比较失败的比赛吧
[题解]
A. Many Equal Substrings
[算法]
我们可以找到这个字符串中最长的后缀 = 前缀的长度(记为T) , 那么 , 最后的答案就是S + S[T + 1..LEN] + S[T + 1..LEN] + .... + S[T + 1,LEN](共(k - 1)个S[T + 1,LEN]) , 最长后缀 = 前缀的长度我们可以通过kmp的next数组求出 , 当然 , 由于n比较小,朴素算法也是可以实现的
由于答案字符串的长度很短 , 我们也可以直接模拟 , 笔者在比赛时就采取了这种做法
时间复杂度 : O(N)
[代码]
#include<bits/stdc++.h> using namespace std; int n,k,len; string t,ans; template <typename T> inline void read(T &x) { int f = 1; x = 0; char c = getchar(); for (; !isdigit(c); c = getchar()) { if (c == '-') f = -f; } for (; isdigit(c); c = getchar()) x = (x << 3) + (x << 1) + c - '0'; x *= f; } int main() { cin >> n >> k; cin >> t; ans = t; int sum = 1 , now = 1; while (true) { if (sum == k) break; if (now == (int)ans.size()) ans += t; else if (now + n - 1 >= (int)ans.size()) { if (now != (int)ans.size() && ans.substr(now,(int)ans.size() - now) != t.substr(0,(int)ans.size() - now)) now++; else ans += t[(int)ans.size() - now]; } else if (ans.substr(now,n) == t.substr(0,n)) { sum++; now++; } else now++; } cout<< ans << endl; return 0; }
B. Creating the Contest
[算法]
我们可以扫描一遍数组 , 当扫描到一个元素时 , 我们贪心地将该元素加入满足条件的 , 大小最大的组中 , 如果不存在满足条件的组就新建一个 , 最后的答案为最大的组的大小
时间复杂度 : O(N)
[代码]
#include<bits/stdc++.h> using namespace std; const int MAXN = 2 * 1e5 + 10; int n,tot; int g[MAXN],a[MAXN],sz[MAXN]; template <typename T> inline void read(T &x) { int f = 1; x = 0; char c = getchar(); for (; !isdigit(c); c = getchar()) { if (c == '-') f = -f; } for (; isdigit(c); c = getchar()) x = (x << 3) + (x << 1) + c - '0'; x *= f; } int main() { read(n); for (int i = 1; i <= n; i++) read(a[i]); g[tot = 1] = a[1]; sz[1] = 1; for (int i = 2; i <= n; i++) { int pos = -1; for (int j = 1; j <= tot; j++) { if (a[i] <= g[j] * 2) { if (pos == -1 || sz[j] > sz[pos]) pos = j; } } if (pos != -1) { g[pos] = a[i]; sz[pos]++; } else { g[++tot] = a[i]; sz[tot] = 1; } } int ans = 0; for (int i = 1; i <= tot; i++) ans = max(ans,sz[i]); printf("%d ",ans); return 0; }
C. Maximal Intersection
[算法]
首先 , n条线段的交集长度为[Lmax,Rmin] , 其中Lmax为最靠右的左端点,Rmin为最靠左的右端点,若Lmax > Rmin , 说明这些线段无交集
发现这个性质以后 , 我们可以枚举删去哪一条线段, 在剩余线段中找到最靠右的左端点和最靠左的右端点 ,用两者的差值更新答案,具体实现时,
我们可以记录前缀最值和后缀最值, 当然 , STL中的multiset和线段树等数据结构也是可以完成这个任务的
时间复杂度 : O(N) 或 O(N log N)
[代码]
#include<bits/stdc++.h> using namespace std; const int MAXN = 3e5 + 10; const int inf = 2e9; int n; int l[MAXN],r[MAXN],premn[MAXN],sufmn[MAXN],premx[MAXN],sufmx[MAXN]; template <typename T> inline void read(T &x) { int f = 1; x = 0; char c = getchar(); for (; !isdigit(c); c = getchar()) if (c == '-') f = -f; for (; isdigit(c); c = getchar()) x = (x << 3) + (x << 1) + c - '0'; x *= f; } int main() { read(n); for (int i = 1; i <= n; i++) { read(l[i]); read(r[i]); } if (n == 1) { printf("%d ",r[1] - l[1]); return 0; } premx[0] = -inf; for (int i = 1; i <= n; i++) premx[i] = max(premx[i - 1],l[i]); sufmx[n + 1] = -inf; for (int i = n; i >= 1; i--) sufmx[i] = max(sufmx[i + 1],l[i]); premn[0] = inf; for (int i = 1; i <= n; i++) premn[i] = min(premn[i - 1],r[i]); sufmn[n + 1] = inf; for (int i = n; i >= 1; i--) sufmn[i] = min(sufmn[i + 1],r[i]); int ans = 0; for (int i = 1; i <= n; i++) ans = max(ans,min(premn[i - 1],sufmn[i + 1]) - max(premx[i - 1],sufmx[i + 1])); printf("%d ",ans); return 0; }
D.Concatenated Multiples
[算法]
不难发现 , 所有符合条件的二元组(i,j)都满足 : 10^(len(j)) * a[i] + a[j] 是k的倍数 ,我们不妨开10个std :: map , 其中,第m个map记录的是10^m * a[t] ( 1 <= t <= n)对k取模为V(0 <= V < k)的数的个数 , 然后我们枚举每一个数更新答案 , 最后 , 我们还需将答案减去(i,i)这样的
不合法的二元组
时间复杂度 : O(10Nlog(N))
[代码]
此题时限很紧 , 注意优化常数 !
#include<bits/stdc++.h> using namespace std; #pragma GCC optimize("O2") #pragma GCC optimize("O3") #pragma GCC optimize("Ofast") const int MAXN = 2 * 1e5 + 10; typedef long long ll; int n; ll k,ans; map< ll,int > mp[11]; ll a[MAXN]; template <typename T> inline void read(T &x) { int f = 1; x = 0; char c = getchar(); for (; !isdigit(c); c = getchar()) { if (c == '-') f = -f; } for (; isdigit(c); c = getchar()) x = (x << 3) + (x << 1) + c - '0'; x *= f; } inline int len(ll n) { int ret = 0; while (n > 0) { ret++; n /= 10; } return ret; } inline ll Pow(int a,int n) { ll ret = 1; for (int i = 1; i <= n; i++) ret = 1ll * ret * a % k; return ret; } int main() { read(n); read(k); for (int i = 1; i <= n; i++) read(a[i]); for (int i = 1; i <= n; i++) { for (int j = 1; j <= 10; j++) { mp[j][1ll * Pow(10,j) * 1ll * a[i] % k]++; } } for (int i = 1; i <= n; i++) if (mp[len(a[i])].count((k - a[i] % k) % k)) ans += mp[len(a[i])][(k - a[i] % k) % k]; for (int i = 1; i <= n; i++) { if ((1ll * Pow(10,len(a[i])) * a[i] % k + 1ll * a[i]) % k == 0) ans--; } printf("%I64d ",ans); return 0; }
E. Tree with Small Distances
[算法]
首先将所有的深度大于2的点加入一个堆中 , 每次我们找到深度最大的点 , 将这个点从堆中弹出 , 然后 , 我们将这个点的父节点和1号节点连一条边 , 在堆中删除所有与该节点父节点相连的边
当堆中没有元素时, 就得到了答案
时间复杂度 : O(NlogN)
[代码]
#include<bits/stdc++.h> using namespace std; const int MAXN = 2e5 + 10; const int inf = 2e9; int n,tot,ans; int head[MAXN],fa[MAXN],dep[MAXN]; priority_queue< pair<int,int> > q; bool visited[MAXN]; struct edge { int to,nxt; } e[MAXN << 1]; template <typename T> inline void read(T &x) { int f = 1; x = 0; char c = getchar(); for (; !isdigit(c); c = getchar()) if (c == '-') f = -f; for (; isdigit(c); c = getchar()) x = (x << 3) + (x << 1) + c - '0'; x *= f; } inline void addedge(int u,int v) { tot++; e[tot] = (edge){v,head[u]}; head[u] = tot; } inline int dfs(int u,int par) { for (int i = head[u]; i; i = e[i].nxt) { int v = e[i].to; if (v == par) continue; fa[v] = u; dep[v] = dep[u] + 1; dfs(v,u); } } int main() { read(n); for (int i = 1; i < n; i++) { int u,v; read(u); read(v); addedge(u,v); addedge(v,u); } dfs(1,0); for (int i = 1; i <= n; i++) { if (dep[i] > 2) q.push(make_pair(dep[i],i)); } while (!q.empty()) { int u = q.top().second; q.pop(); if (visited[u]) continue; visited[u = fa[u]] = true; ans++; for (int i = head[u]; i; i = e[i].nxt) { int v = e[i].to; visited[v] = true; } } printf("%d ",ans); return 0; }
F. Multicolored Markers
[算法]
首先, 大长方形的边长一定是(a + b)的约数, 所以 , 我们可以从1到sqrt(a + b)枚举较小的那些因子(其中,sqrt表示开根号) , 求出(a + b)的所有约数
既然我们要求至少有一种颜色的格子满足这些格子也能构成长方形 , 那么 , 我们可以同样用上述方法求出a和b的所有因子
为了方便讨论,我们假设所有红色颜色的格子能够构成长方形
然后 , 我们枚举大正方形的长d1 , 用二分或Two Pointers求出小于d1的 , 最大的a的约数 , 如果((a + b) / d1 >= a / d2) , 我们就用(d1 + (a + b) / d1) * 2更新答案
蓝色格子同理
时间复杂度 : O(sqrt(a + b)) (其中 , sqrt表示开根号 )
[代码]
#include<bits/stdc++.h> using namespace std; const int MAXD = 1e5 + 10; typedef long long ll; const ll inf = 1e18; ll a,b,ans,lena,lenb; ll fa[MAXD],fb[MAXD]; template <typename T> inline void read(T &x) { ll f = 1; x = 0; char c = getchar(); for (; !isdigit(c); c = getchar()) { if (c == '-') f = -f; } for (; isdigit(c); c = getchar()) x = (x << 3) + (x << 1) + c - '0'; x *= f; } int main() { read(a); read(b); ll tmp = a + b; for (ll i = 1; 1ll * i * i <= a + b; i++) { if (tmp % i == 0) { fa[++lena] = i; if (1ll * i * i != a + b) fa[++lena] = (a + b) / i; } } for (ll i = 1; 1ll * i * i <= a; i++) { if (a % i == 0) { fb[++lenb] = i; if (1ll * i * i != a) fb[++lenb] = a / i; } } sort(fb + 1,fb + lenb + 1); fb[++lenb] = inf; ans = inf; for (int i = 1; i <= lena; i++) { ll d1 = fa[i]; int pos = lower_bound(fb + 1,fb + lenb + 1,d1) - fb; if (fb[pos] > d1 && pos == 1) continue; if (fb[pos] > d1) pos--; ll d2 = fb[pos]; if ((a + b) / d1 >= a / d2) ans = min(ans,2 * (d1 + (a + b) / d1)); } lenb = 0; for (ll i = 1; 1ll * i * i <= b; i++) { if (b % i == 0) { fb[++lenb] = i; if (1ll * i * i != b) fb[++lenb] = b / i; } } sort(fb + 1,fb + lenb + 1); fb[++lenb] = inf; for (int i = 1; i <= lena; i++) { ll d1 = fa[i]; int pos = lower_bound(fb + 1,fb + lenb + 1,d1) - fb; if (fb[pos] > d1 && pos == 1) continue; if (fb[pos] > d1) pos--; ll d2 = fb[pos]; if ((a + b) / d1 >= b / d2) ans = min(ans,2 * (d1 + (a + b) / d1)); } printf("%I64d ",ans); return 0; }
[总结]
1. 这次比赛的失利不仅是因为时差的问题 , 我认为 , 更多的还是我的基本功不够扎实 , 值得反思
2. 突然发现打比赛也是一种提高水平的好方法 , 每次比赛都能发现自己的薄弱点并订正试题 , 查漏补缺 , 以后要多参加这些比赛 , 不仅提高了能力 , 而且 , 英语水平也能得到提高
3. 离noip2018只剩两个月了 , 在剩下的两个月中 , 我还需更加努力 , 调整好心态 , 争取最后取得理想的成绩