CF 1500-1800训练 A - M 训练记录
题目链接
A - Decrease the Sum of Digits
思路:
记录以下到那个位置会大于 \(s\),然后前一个位置的数加一,之后的全部变为零即可。
注意特判到某个位置刚好等于 \(s\) 的情况。
代码:
int main(){
int t; cin >> t;
while(t --){
string str; int s;
cin >> str >> s;
ll cur = 0;
int lenStr = str.size();
for(int i = 0; i < lenStr; i ++) cur = cur * 10 + str[i] - '0';
int sum = 0, idx = -1;
for(int i = 0; i < lenStr; i ++){
sum += str[i] - '0';
if(sum > s){
idx = i;
break;
} else if(sum == s){
for(int j = i + 1; j < lenStr; j ++) sum += str[j] - '0';
if(sum > s){
idx = i;
}
break;
}
}
if(idx == -1) puts("0");
else{
//cout << "idx = " << idx << endl;
if(idx == 0){
str = '1' + str;
idx ++;
} else{
str[idx - 1] ++;
}
lenStr = str.size();
for(int i = idx; i < str.size(); i ++) str[i] = '0';
ll now = 0;
//cout << str << endl;
for(int i = 0; i < lenStr; i ++) now = now * 10 + str[i] - '0';
cout << now - cur << endl;
}
}
return 0;
}
B - Two Platforms
思路:
显然 \(y\) 坐标是没有用的。
将 \(x\) 坐标排序,预处理出 \(cnt[i]\) 数组,含义是 \(i\) 到 \(n\) 之间放一个平台可以覆盖最多的点的数量。
然后遍历一遍即可。
代码:
const int N = 2e5 + 10;
int x[N], cnt[N];
int main(){
int t; cin >> t;
while(t --){
int n, k; cin >> n >> k;
memset(cnt, 0, sizeof cnt);
for(int i = 1; i <= n; i ++) scanf("%d", &x[i]);
for(int i = 1, y; i <= n; i ++) scanf("%d", &y);
sort(x + 1, x + n + 1);
for(int i = n; i >= 1; i --){
int mx = x[i] + k;
int idx = upper_bound(x + 1, x + n + 1, mx) - x;
cnt[i] = max(cnt[i + 1], idx - i);
}
int ans = 0;
for(int i = 1; i <= n; i ++){
int mx = x[i] + k;
int idx = upper_bound(x + 1, x + n + 1, mx) - x;
ans = max(ans, (idx - i) + cnt[idx]);
}
cout << ans << endl;
}
return 0;
}
C - Maximum Distributed Tree
思路:
把每条边的贡献算出来,然后贪心的赋值。
代码:
const int N = 1e5 + 10;
const int mod = 1e9 + 7;
vector<int> g[N];
ll cnt[N];
ll p[N], q[N];
void dfs(int cur, int fa){
cnt[cur] = 1;
for(auto it : g[cur]){
if(it == fa) continue;
dfs(it, cur);
cnt[cur] += cnt[it];
}
}
int main(){
int t; cin >> t;
while(t --){
int n, m; cin >> n;
for(int i = 1; i <= n; i ++){
g[i].clear();
cnt[i] = 0;
p[i] = 1;
}
for(int i = 1, u, v; i < n; i ++){
scanf("%d %d", &u, &v);
g[u].pb(v); g[v].pb(u);
}
cin >> m;
for(int i = 1; i <= m; i ++) scanf("%lld", p + i);
dfs(1, -1);
m = max(m, n - 1);
sort(p + 1, p + m + 1);
for(int i = n; i <= m; i ++) p[n - 1] = p[n - 1] * p[i] % mod;
for(int i = 1; i < n; i ++) q[i] = cnt[i + 1] * (n - cnt[i + 1]);
sort(q + 1, q + n);
ll ans = 0;
for(int i = 1; i < n; i ++)
ans = (ans + (q[i] % mod * p[i] % mod) % mod) % mod;
cout << ans << endl;
}
return 0;
}
D - RPG Protagonist
思路:
由于 \(cnts\) 不是很大,所以直接枚举某一个人选择 \(sword\) 的数量即可。
代码:
int main(){
int t; input(t);
while(t --){
ll p, f, cnts, cntw, s, w;
input(p); input(f);
input(cnts); input(cntw);
input(s); input(w);
ll ans = 0;
for(int i = 0; i <= cnts; i ++){
ll tmp = i;
ll rp = p - i * s;
if(rp < 0) break;
ll rcnts = cnts - i;
ll getw = min(cntw, rp / w);
ll rcntw = cntw - getw;
tmp += getw;
ll t1 = 0, t2 = 0;
if(w < s){
ll getw = min(rcntw, f / w);
t1 = getw;
ll rf = f - getw * w;
t1 += min(rcnts, rf / s);
} else{
ll gets = min(rcnts, f / s);
t2 = gets;
ll rf = f - gets * s;
t2 += min(rcntw, rf / w);
}
tmp += max(t1, t2);
ans = max(ans, tmp);
}
cout << ans << endl;
}
return 0;
}
E - Binary String To Subsequences
思路:
分别记录出 \(0, 1\) 出现的位置,然后遍历一遍,对于当前出现的 \(s[i]\),直接二分去找下一个应该出现的字符(\(0\) 找 \(1\),\(1\) 找 \(0\)),对于找到的字符,可以从记录的容器中删去。
代码:
const int N = 2e5 + 10;
char s[N];
int ans[N];
vector<int> zero, one;
int main(){
int t; input(t);
while(t --){
int n; input(n);
input(s + 1);
for(int i = 1; i <= n; i ++){
if(s[i] == '0') zero.pb(i);
else if(s[i] == '1') one.pb(i);
}
memset(ans, 0, sizeof ans);
int k = 0;
for(int i = 1; i <= n; i ++){
if(ans[i] == 0) ans[i] = ++ k;
if(s[i] == '1'){
int idx = upper_bound(all(zero), i) - zero.begin();
if(idx >= zero.size()) continue;
if(zero[idx] > i && zero[idx] <= n) {
ans[zero[idx]] = ans[i];
zero.erase(zero.begin() + idx);
}
} else{
int idx = upper_bound(all(one), i) - one.begin();
if(idx >= one.size()) continue;
if(one[idx] > i && one[idx] <= n) {
ans[one[idx]] = ans[i];
one.erase(one.begin() + idx);
}
}
}
output(k);
for(int i = 1; i <= n; i ++) printf("%d ", ans[i]);
puts("");
zero.clear();
one.clear();
}
return 0;
}
F - Colored Rectangles
思路:
直接贪心的错误样例:
r: 3 3
g: 4
b: 5
wa = 20
ac = 27
用 \(dp\) 暴力则可以完整的枚举出所有的情况。
设 \(f[i][j][k]\) :红色用来 \(i\) 个,绿色用了 \(j\) 个,蓝色用了 \(k\) 个的最大值。
转移方程:
代码:
const int N = 210;
int R[N], G[N], B[N];
ll f[N][N][N];
int main(){
int r, g, b;
input(r); input(g); input(b);
for(int i = 1; i <= r; i ++) input(R[i]);
for(int i = 1; i <= g; i ++) input(G[i]);
for(int i = 1; i <= b; i ++) input(B[i]);
auto cmp = [&](int a, int b) -> bool {
return a > b;
};
sort(R + 1, R + r + 1, cmp);
sort(G + 1, G + g + 1, cmp);
sort(B + 1, B + b + 1, cmp);
ll ans = 0;
for(int i = 1; i <= r + 1; i ++)
for(int j = 1; j <= g + 1; j ++)
for(int k = 1; k <= b + 1; k ++){
f[i + 1][j + 1][k] = max(f[i + 1][j + 1][k], f[i][j][k] + R[i] * G[j]);
f[i + 1][j][k + 1] = max(f[i + 1][j][k + 1], f[i][j][k] + R[i] * B[k]);
f[i][j + 1][k + 1] = max(f[i][j + 1][k + 1], f[i][j][k] + B[k] * G[j]);
ans = max(ans, f[i][j][k]);
}
output(ans);
return 0;
}
G - Good Subarrays
思路:
思维 + 前缀和
已知:\(sum[i] - sum[j - 1] = i - j + 1 = i - (j - 1)\)
那么:\(sum[i] - i = sum[j - 1] - (j - 1)\)
显然我们只需要计算出 \(sum[i] - i\) 的个数即可。
注:由于前面的公式涉及到了 \(idx = 0\) 的情况,所以 \(sum[0] - 0\) 也要算一个。
代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int sum[N];
map<int, int> mp;
int main(){
int t; scanf("%d", &t);
while(t --){
mp.clear();
int n; scanf("%d", &n);
sum[0] = 0;
for(int i = 1; i <= n; i ++){
int x; scanf("%1d", &x);
sum[i] = sum[i - 1] + x;
mp[sum[i] - i] ++;
}
long long ans = 0;
mp[0] ++;
for(auto it : mp) {
ans += 1ll * it.second * (it.second - 1) / 2;
}
cout << ans << endl;
}
return 0;
}
H - Same GCDs
思路:
当 \(a > b\) 时有:\(gcd(a,b) = gcd(a-b,b)\)
那么当 \(a + x >= m\) 时:\(gcd(a + x) = gcd(a + x - m)\).
即 \((a + x) \% m\)。
设 \(y = (a + x) \% m\)
可得:\(gcd(y, m) = gcd(a,m) = d\)
由 \(gcd(y, m) = d\) ,可得: \(gcd(\frac{y}{d}, \frac{m}{d}) = 1\)
设 \(n = \frac{m}{d}\),显然 \(n\) 是已知得,那么我们求得就是 \([2, n]\) 中与 \(n\) 互质的数的个数,
欧拉函数即可解决。
代码:
ll Euler(ll n){
ll ans = n;
for(ll i = 2; i * i <= n; i ++){
if(n % i == 0) ans -= ans / i;
while(n % i == 0) n /= i;
}
if(n > 1) ans -= ans / n;
return ans;
}
int main(){
int t; read(t);
while(t --){
ll a, m;
cin >> a >> m;
ll d = gcd(a, m);
// out(toStr(d), d);
cout << Euler(m / d) << endl;
}
return 0;
}
I - Ehab and Prefix MEXs
思路:
把未出现的数用 \(b\) 数组记录一下。
如果 \(a[i] == a[i - 1]\) 则输出 \(b\) 中最小的数
如果 \(a[i] \ne a[i - 1]\) 则输出 \(a[i - 1]\)
代码:
int a[N], b[N], c[N];
int main(){
memset(c, 0, sizeof c);
int n; cin >> n;
for(int i = 1; i <= n; i ++) { cin >> a[i]; c[a[i]] = 1; }
int cntb = 0;
for(int i = 0; i <= n; i ++) if(!c[i]) b[ ++ cntb] = i;
cntb = 0;
a[0] = a[1];
for(int i = 1; i <= n; i ++){
if(a[i] == a[i - 1]) cout << b[ ++ cntb] << " ";
else cout << a[i - 1] << " ";
}
return 0;
}
J - Skyscrapers (easy version)
思路:
显然要符合要求,那么曲线必然是先上升后下降(单增和单减属于特殊情况)。曲线即是: ^。
那么我们直接暴力枚举最高点即可。
时间复杂度:\(O(n^2)\)。
代码:
int n; cin >> n;
vector<int> ans(n), tmp(n);
ll h = 0;
for(int i = 0; i < n; i ++) cin >> m[i];
for(int i = 0; i < n; i ++){
ll sum = 0;
tmp[i] = m[i];
sum += tmp[i];
for(int j = i + 1; j < n; j ++) { tmp[j] = min(m[j], tmp[j - 1]); sum += tmp[j]; }
for(int j = i - 1; j >= 0; j --) { tmp[j] = min(m[j], tmp[j + 1]); sum += tmp[j]; }
if(sum > h){
ans = tmp;
h = sum;
}
}
for(int i = 0; i < n; i ++) cout << ans[i] << " ";
cout << endl;
K - Yet Another Walking Robot
思路:
用一个 \(map\) 来记录当前移动的状态,如果当前出现的状态在之前就已经出现过了,那么这一段的子串就可以合法删除。
代码:
const int N = 2e5 + 10;
char s[N];
int main(){
int t; read(t);
while(t --){
int n; read(n);
scanf("%s", s + 1);
int x = 0, y = 0;
map<PII, int> mp;
int d = INF;
PII ans;
mp[{0, 0}] = 0;
for(int i = 1; i <= n; i ++){
if(s[i] == 'L') x --;
else if(s[i] == 'R') x ++;
else if(s[i] == 'U') y --;
else y ++;
if(mp.count({x, y}) && d > i - mp[{x, y}]){
d = i - mp[{x, y}];
ans = {mp[{x, y}] + 1, i};
}
mp[{x, y}] = i;
}
if(d == INF) puts("-1");
else printf("%d %d\n", ans.x, ans.y);
}
return 0;
}
L - Fight with Monsters
思路:
记录出每次都是我打死怪兽需要的最少令牌数,然后排个序,即可求出答案。
代码:
ll calc(ll a, ll b, ll x){
ll t = x / (a + b);
ll all = t * (a + b);
if(all == x) t --;
x -= t * (a + b);
return x % a == 0 ? x / a - 1 : x / a;
}
int main(){
ll n, a, b, k;
cin >> n >> a >> b >> k;
vector<ll> vec;
for(int i = 1; i <= n; i ++){
ll x; read(x);
vec.pb(calc(a, b, x));
}
sort(all(vec));
int ans = 0;
for(int i = 0; i < sz(vec); i ++){
k -= vec[i];
if(k >= 0) ans ++;
else break;
}
cout << ans << endl;
return 0;
}
M - String Coloring (easy version)
思路:
当存在三个字符满足以下规律时:\(z\ge y\ge x\)(\(x, y, z是变量\)),可以发现此时是无解的。所以我们可以根据这个来判断。
根据以上的结论可以想到原字符串最多可以分为两个不递减子序列。
代码:
const int N = 2e2 + 10;
char s[N];
int main(){
int n; read(n);
scanf("%s", s + 1);
string ans = "";
char mx1 = 'a', mx2 = 'a';
for(int i = 1; i <= n; i ++){
if(s[i] >= mx1) { mx1 = s[i]; ans += '1'; }
else if(s[i] >= mx2) { mx2 = s[i]; ans += '0'; }
else {
puts("NO");
return 0;
}
}
puts("YES");
cout << ans << endl;
return 0;
}