[CF1902] Educational Codeforces Round 159 A~E 题解
[CF1902] Educational Codeforces Round 159 A~E 题解
A. Binary Imbalance
很快观察到如果有不同的相邻元素,那么一定有解,意味着如果全是 1 无解,其他有解
B. Getting Points
题面很长,可以发现,最好的偷懒方式一定是把所有的课都拖到最后几天上(真实),可以简单调整证明这样是不劣的,最后几天上的课有三种课:
- 完成两个任务
- 完成一个任务
- 不做任务
推推式子就出了。
void work() {
int n, p, a, b;
cin >> n >> p >> a >> b;
int t = (n - 1) / 7 + 1;
int cnt = t / 2;
int ans = 0;
if(cnt * (a + 2 * b) >= p) {
cout << n - ((p + a + 2 * b - 1) / (a + 2 * b)) << '\n';
return ;
}
p -= cnt * (a + 2 * b);
ans += cnt;
if((t % 2 == 1) && a + b >= p) {
cout << n - 1 - cnt << '\n';
return ;
}
else if((t % 2 == 1)) {
ans ++;
p -= a + b;
}
cout << n - ans - ((p + a - 1) / a) << '\n';
return ;
}
C. Insert and Equalize
考虑如果不加入新元素是什么情况。
那么最后一定会等于最大数,否则可以同时减去 \(x\) 这样更优。
那么这个 \(x\) 就必须整除所有元素和最大数的差,取个 \(\gcd\) 即可。
考虑加入新元素,那么显然不应该使 \(x\) 变小,所以只有两种情况。
- \(a_{n + 1} > a_n\) 此时 \(a_{n + 1} = a_n + x\)。
- \(a_{n + 1} < a_n\),此时 \(a_{n + 1} = a_n - kx\),\(k\) 是使得 \(a_{n + 1}\) 不重复的最小的正整数。
取 \(\min\) 即可。
void work() {
cin >> n;
s.clear();
for(int i = 1; i <= n; i ++) cin >> a[i], s.insert(a[i]);
if(n == 1) { // 要读完 a1 再return 不然会吃两发罚时
cout << 1 << '\n';
return ;
}
sort(a + 1, a + n + 1);
int ans = 0, d = a[2] - a[1];
for(int i = 3; i <= n; i ++) {
d = gcd(d, a[i] - a[i - 1]);
}
for(int i = 1; i <= n; i ++)
ans += (a[n] - a[i]) / d;
int pos = 1e9;
for(int i = 1; i <= n + 1; i ++)
if(s.find(a[n] - i * d) == s.end()) {
pos = i;
break;
}
cout << ans + min(n, pos) << '\n';
return ;
}
D. Robot Queries
先模拟处理出 \(p_i\) 表示第 \(i\) 次执行后机器人坐标。
观察到反转操作实际上是把所有原路径上的点关于 \(p_l, p_r\) 的中点做对称,不妨把查询的 \((x, y)\) 关于中点做对称再直接查询原路径上的点,由对称性,这样是等价的。
所以我们现在需要维护在 \([l, r]\) 时刻,\((x, y)\) 是否被经过。
观察到值域比较小,可以使用 vector
维护每一个点在哪些时刻被经过,查询的时候二分 \(l, r\) 即可。
vector<int> c[N];
bool calc(int l, int r, int t) {
int lp = lower_bound(c[t].begin(), c[t].end(), l) - c[t].begin(), rp = upper_bound(c[t].begin(), c[t].end(), r) - c[t].begin() - 1;
return lp <= rp;
}
signed main() {
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
cin >> n >> q >> s;
c[id({0, 0})].push_back(0);
for(int i = 0, x = 0, y = 0; i < n; i ++) {
if(s[i] == 'U') y ++;
if(s[i] == 'D') y --;
if(s[i] == 'L') x --;
if(s[i] == 'R') x ++;
p[i + 1] = {x, y};
c[id({x, y})].push_back(i + 1);
}
for(int i = 1, x, y, l, r; i <= q; i ++) {
cin >> x >> y >> l >> r;
if(calc(0, l - 1, id({x, y})) || calc(r, n, id({x, y})) || calc(l, r - 1, id({p[l - 1].x + p[r].x - x, p[l - 1].y + p[r].y - y})))
cout << "YES\n";
else
cout << "NO\n";
}
return 0;
}
E. Collapsing Strings
首先正难则反方便做,用总字符串长度减去被消掉的长度,观察到 \(C(a, b)\) 函数的本质是求 \(LCP(rev_a, b)\),\(rev_a\) 表示 \(a\) 的反转。
看到前缀考虑 Trie,插入所有字符串的反转,在插入的路径上增加 1 的贡献,意味着多匹配了一个字符,之后枚举 \(b\),在 Trie 上跑匹配,统计路径上所有的贡献。
最后需要 \(\times 2\),因为这样只会统计 \(b\) 作为第二个字符串的贡献。
#include <algorithm>
#include <cstring>
#include <iostream>
#include <queue>
// #define int long long
#define x first
#define y second
using namespace std;
typedef pair<int, int> PII;
typedef long long ll;
const int N = 1e6 + 10, M = 2e6 + 10;
int tr[M][26], cnt[M], idx;
void update(string s) {
int u = 0;
for(int i = (int)s.size() - 1; i >= 0; i --) {
int c = s[i] - 'a';
if(!tr[u][c]) tr[u][c] = ++ idx;
u = tr[u][c];
cnt[u] ++;
}
}
int query(string s) {
int u = 0, ans = 0;
for(int i = 0; i < s.size(); i ++) {
int c = s[i] - 'a';
if(tr[u][c]) u = tr[u][c];
else break;
ans += cnt[u];
}
return ans;
}
int n;
string s[N];
signed main() {
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
cin >> n;
for(int i = 1; i <= n; i ++) cin >> s[i], update(s[i]);
long long ans = 0;
for(int i = 1; i <= n; i ++) ans += s[i].size();
ans = ans * 2 * n;
for(int i = 1; i <= n; i ++)
ans -= 2 * query(s[i]);
cout << ans << '\n';
return 0;
}
总结
B 一开始没看懂题,切慢了一点,不过亮点是把所有情况都讨论了一发过,C 交了两发罚时,以后注意一下特判的时候要保证所有的数据都读完了。
E 莽对了一发过,但是 D 观察到中点对称的性质之后没有想到怎么维护,看了 \(\text{\color{black}g\color{red}yh20}\) 大佬的代码之后恍然大悟。