CF2157
超级失败的一场~
CF2157D Billion Players Game
首先,所有的点都得往中间指。因为如果一个右边的点向右指,左边的点向左指的话,那只要 \(p\) 取在这两个中间就一定会让代价减少。反之,如果让两个点配对向中间指,不论 \(p\) 在何处,贡献一定是线段长度。
\(a_i < l\) 的点肯定往右指,\(a_i > r\) 的点肯定向左指。那么基础答案就是 \(\sum_{a_i < l} l - a_i + \sum_{a_i > r} r - a_i\)。
然后对于 \(a_i\) 在中间的点,仍然会产生贡献。同时,我们还要考虑现在有 \(b\) 个 \(a_i < l\) 的点,\(c\) 个 \(a_i > r\) 的点,那么就相当于 \(b\) 个点在 \(l\) 处,\(c\) 个点在 \(r\) 处。然后两两配对,最大化绝对值之和。排序后小配大即可。
贪心策略错时不妨拍一拍,让自己清楚地意识到假在哪,能不能修。
Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 2e5 + 5;
ll a[N], n, l, r, p[N], tp;
void solve(){
cin >> n >> l >> r;
for(int i = 1; i <= n; ++i) cin >> a[i];
tp = 0;
sort(a + 1, a + 1 + n);
ll ans = 0;
int x = 1, y = n;
while(x <= n && a[x] < l) p[++tp] = l, ans += l - a[x], ++x;
while(y >= 1 && a[y] > r) p[++tp] = r, ans += a[y] - r, --y;
for(int i = x; i <= y; ++i) p[++tp] = a[i];
sort(p + 1, p + 1 + n);
for(int i = 1; 2 * i <= n; ++i) ans += p[n - i + 1] - p[i];
cout << ans << '\n';
}
int main(){
cin.tie(0)->sync_with_stdio(0);
int T; cin >> T;
while(T--) solve();
return 0;
}
CF2157E Adjusting Drones
Haybale Guessing 同款的并查集。
首先二分答案,变成判断 \(t\) 次之后是否合法。我们按值域考虑肯定没问题。假设 \(a_i\) 有 \(b_i\) 个。按 \(a_i\) 从大到小处理,我们发现只要后面比 \(a_i\) 大的那些数不在 \(a_i\) adjust 的时候跟它撞上,那么每次 \(a_i\) 自增之后的数量都会减 1。对于那些撞上的位置,发现上面一定只有一个无人机,因此数量不增不减。那么就变成染色问题了,跳到的空的个数就是可以减少的个数。验证最后减少的次数能不能让 \(b_i < k\) 即可。
复杂度 \(O(N \log N)\)。
Code
#include <bits/stdc++.h>
using namespace std;
const int N = 6e5 + 10;
int a[N], b[N], n, V, k, cnt[N], f[N];
void init(){ for(int i = 1; i <= 3 * V + 5; ++i) f[i] = i; }
int getf(int p){ return (p == f[p] ? p : f[p] = getf(f[p]));}
bool check(int t){
init();
for(int i = 1; i <= n; ++i) b[i] = cnt[a[i]];
for(int i = n; i >= 1; --i){
f[a[i]] = a[i] + 1;
int p = getf(a[i]);
for(; p <= a[i] + t; p = getf(p)){
if(b[i] == 1) break;
b[i]--;
f[p] = p + 1;
}
if(b[i] > k) return 0;
}
return 1;
}
void solve(){
cin >> n >> k;
int l = 0, r = n; V = n;
for(int i = 1; i <= 2 * n; ++i) cnt[i] = 0;
for(int i = 1; i <= n; ++i) cin >> a[i], cnt[a[i]]++;
sort(a + 1, a + 1 + n);
n = unique(a + 1, a + 1 + n) - a - 1;
while(l < r){
int mid = (l + r) >> 1;
if(check(mid)) r = mid;
else l = mid + 1;
}
cout << l << '\n';
}
int main(){
cin.tie(nullptr)->sync_with_stdio(0);
int T; cin >> T;
while(T--) solve();
return 0;
}
CF2157F Git Gud
一个策略是,我们先把所有数调成整除 2,然后再调成整除 4......
注意这里的整除指的是与 \(n\) 的差。
具体来说就是先 \(n - 1, n - 3, n - 5, \cdots\),每个加的值都是 1。这样都变成整除 2 了。然后再 \(n - 2, n - 6, n - 12, \cdots\),每个加的值都是 2。这样都变成整除 4 了。以此类推。
这样每层的代价是 \(1000 + O(n)\),因为两层衔接的话需要一次递增。那么总共就是 \(1000O(\log n) + O(n \log n)\)。
还是不够优。
实际上,如果我们选 \(m\) 作为模数,先调成整除 \(m\),然后是 \(m^2\),这样做下去。对于第一层的构造方案就是
每个都加 1,这样就用 \(1000(m - 1) + n\) 的代价。现在所有数都整除 \(m\) 了。
对于第二层的情况,也是类似的,就是每个 \(+m\)。
这样代价总和就是 \(\log_mn(1000(m - 1) + n)\),取大概 \(m = n^{\frac{1}{3}}\) 可以通过。
Code
#include <bits/stdc++.h>
using namespace std;
const int N = 2.5e5 + 5;
int a[N], b[N], tp, n, m, fl = 0;
void solve(int k){
int y = pow(m, k), x = y / m;
if(x > n) return;
for(int i = 1; i < m; ++i){
for(int j = n - y + i * x; j >= 1; j -= y){
a[++tp] = j, b[tp] = x;
}
}
solve(k + 1);
}
int main(){
cin.tie(nullptr)->sync_with_stdio(0);
cin >> n;
m = ceil(pow(n, 1. / 3.));
solve(1);
cout << tp << '\n';
for(int i = 1; i <= tp; ++i) cout << a[i] << ' ' << b[i] << '\n';
return 0;
}

浙公网安备 33010602011771号