CF2154
CF2154C2 No Cost Too Great
枚举 \(1\) 到 \(n\) 所有数的质因数的复杂度是 \(O(n \log \log n)\) 的,因为就是埃筛的复杂度。所以想说,这种枚举类的数论题还是要对各种数论函数的数量级有所把握。
Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 4e5 + 5;
const ll inf = 1e18;
int n, pri[N], cnt;
ll a[N], b[N];
vector<int> d[N];
bitset<N> vis;
struct node{
ll fi = inf, se = inf;
void ins(ll x){
if(x < fi) se = fi, fi = x;
else if(x < se) se = x;
}
}buc[N], p;
void solve(){
cin >> n;
p = {inf, inf};
for(int i = 1; i <= n; ++i) cin >> a[i];
for(int i = 1; i <= n; ++i) cin >> b[i], p.ins(b[i]);
ll mn = p.fi + p.se, k = (p.fi == p.se ? 1e18 : p.fi);
// cout << mn << ' ' << k << '\n';
for(int i = 1; i <= n; ++i){
if(k == b[i]){
for(int j = 1; j <= cnt; ++j){
int x = pri[j] - (a[i] % pri[j]);
if(x == pri[j]) x = 0;
buc[pri[j]].ins(x * b[i]);
}
continue;
}
for(int j = 0; j <= 2; ++j){
int x = a[i] + j;
if(b[i] * j > mn) break;
for(int k : d[x]){
vis[k] = 1;
buc[k].ins(b[i] * j);
}
}
for(int j = 0; j <= 2; ++j){
int x = a[i] + j;
if(b[i] * j > mn) break;
for(int k : d[x]){
vis[k] = 0;
}
}
}
ll res = inf;
for(int i = 1; i <= cnt; ++i){
int x = pri[i];
// cout << buc[x].fi << ' ' << buc[x].se << '\n';
res = min(res, buc[pri[i]].fi + buc[pri[i]].se);
buc[pri[i]] = {inf, inf};
}
cout << res << '\n';
}
int main(){
cin.tie(nullptr)->sync_with_stdio(0);
for(int i = 2; i <= 2e5 + 5; ++i){
if(!vis[i]){
pri[++cnt] = i;
d[i].emplace_back(i);
for(int j = 2 * i; j <= 2e5 + 5; j += i)
d[j].emplace_back(i), vis[j] = 1;
}
}
// for(int i = 1; i <= cnt; ++i)
// cout << pri[i] << ' ';
vis.reset();
int T; cin >> T;
while(T--) solve();
return 0;
}
CF2154D Catshock
考虑从叶子开始删点。有一个性质,假设存在一种方案我们在 \(t\) 时刻到达了叶子 \(u\),那么对于所有方案 \(t + 1\) 时刻一定不在点 \(u\) 上。换句话说,这跟奇偶性有关系。因为你如果在 \(u\) 外面绕圈的话,走的边数一定是偶数(一来一回)。
那我们就 dfs,遍历到叶子时就再走一步(走到父亲处),然后把它删掉。对于 \(n \to 1\) 路径上的点也是同理。
这样操作次数刚好就是进去时一步,删掉一步,出来时一步 \(\le 3n\)。
code
#include <bits/stdc++.h>
using namespace std;
const int N = 6e5 + 5;
vector<int> e[N];
int p[N], n, T, tp, ans[N], ansx[N];
void dfs(int u, int fa){
p[u] = fa;
for(int v : e[u]){
if(v == fa) continue;
dfs(v, u);
}
}
void get(int u, int fa){
bool fl = 0;
for(int v : e[u]){
if(v == fa) continue;
if(v != p[u]){
ans[++tp] = 1;
get(v, u);
ans[++tp] = 1, ans[++tp] = 2, ansx[tp] = v;
}
else fl = 1;
}
if(fl){
ans[++tp] = 1, ans[++tp] = 2, ansx[tp] = u;
get(p[u], u);
}
}
int main(){
cin.tie(nullptr)->sync_with_stdio(0);
cin >> T;
while(T--){
cin >> n;
tp = 0;
for(int i = 1; i <= n; ++i) e[i].clear();
for(int i = 1; i < n; ++i){
int u, v;
cin >> u >> v;
e[u].emplace_back(v);
e[v].emplace_back(u);
}
dfs(n, 0);
get(1, 0);
cout << tp << '\n';
for(int i = 1; i <= tp; ++i){
cout << ans[i];
if(ans[i] == 2) cout << ' ' << ansx[i];
cout << '\n';
}
cout << "-------------------\n";
}
return 0;
}
CF2154E No Mind To Think
先按 \(a\) 从小到大排序。然后我们发现,这个取中位数操作,会把大的拉小,小的拉大。那么我们肯定希望变小的数更少一些。记 \(s = \frac{x - 1}{2}\),那么实际上我们是用 \(a_{i + 1}, \cdots a_{i + s}\) 变成 \(a_i\) 的代价,来换取 \(a_1, \cdots, a_{\min(k \times s, i - 1)}\) 的数全部变成 \(a_i\)。
那么我们枚举 \(i\),然后记 \(f(s) = \sum_{j = 1}^{\min(k \times s, i - 1)} a_i - a_j, g(s) = \sum_{j = i + 1}^{i + s} a_j - a_i\),那么答案就是原来的总和加上 \(f(s)\) 减去 \(g(s)\)。显然 \(f(s)\) 上凸,\(g(s)\) 也下凸,因此他们的差也有凸性,并且斜率等于 0 的位置只出现在答案处,那么三分就做完了。
Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 2e5 + 5;
ll a[N], sum[N];
int n, k, T;
void solve(){
cin >> n >> k;
for(int i = 1; i <= n; ++i) cin >> a[i];
sort(a + 1, a + 1 + n);
for(int i = 1; i <= n; ++i) sum[i] = sum[i - 1] + a[i];
ll ans = sum[n];
for(int i = 1; i <= n; ++i){
int l = 1, r = n - i;
auto f = [&](ll x){
if(x > n - i) return -(ll)1e18;
int t = min(k * x, (ll)i - 1);
return t * a[i] - sum[t] - (sum[i + x] - sum[i] - x * a[i]);
};
while(l < r){
int mid = (l + r) >> 1;
if(f(mid) <= f(mid + 1)) l = mid + 1;
else r = mid;
}
ans = max(ans, sum[n] + f(l));
}
cout << ans << '\n';
}
int main(){
cin.tie(nullptr)->sync_with_stdio(0);
cin >> T;
while(T--) solve();
return 0;
}
CF2154F Bombing
先考虑 \(n^2\) 做法。枚举 \(k\),我们发现除了 \(1, 2, \cdots n\) 这一种,其他排列的分界点都是唯一的。看起来就像是把 \(1, 2, \cdots, k\) 和 \(k + 1, \cdots n\) 插在一起了一样,那么我们记前一半序列叫 \(l\),后一半序列叫 \(r\)。
对于给定的 \(k\),我们遍历所有 \(a_i \ne -1\),记录 \(l\) 最大放到哪里,\(r\) 最大放到哪里,那么也就知道 \(a_i\) 到上一个不为 -1 的这一段空之间要放多少个 \(l / r\) 中的数了。简单组合数。
先把原序列可能是排序后序列的情况讨论掉。首先一个长度为 \(len\) 的全是 -1 的序列方案数是 \(2^{len} - len\),除掉排序后序列就再减 1。然后我们发现,一段连续的 -1 中放的数的集合是固定的,并且如果它们最后不是按照排序后序列放的话,其左右两边都必须按顺序放了,那么答案就是 \(\sum_{len} (2^{len} - len - 1) + 1\)。
对于其他的一般情况,我们发现对于 \(a_i \ne -1\) 属于 \(l\) 还是属于 \(r\) 序列已经固定了。因为 \(a_i > i\) 的肯定放在 \(r\) 序列,\(a_i < i\) 的肯定放在 \(l\) 序列。而 \(a_i = i\) 实际上代表着要么它之前必须是 \(1 \cdots i\),要么他之后必须是 \(i \cdots n\)。所以找到第一个 \(a_i \ne i\) 的位置 \(p\),和最后一个位置 \(q\),那么 \(p\) 之前的所有 \(a_i = i\) 一定属于 \(l\),\(q\) 之后的所有 \(a_i = i\) 一定属于 \(r\),而一个合法的序列一定满足 \([p, q]\) 之间的所有 \(a_i\) 不等于 \(i\)。
接下来我们把每段 -1(称为一个空隙)分开来计数。对于一段空隙的开始的那个数是 \(l\),结束的那个数也是 \(l\),那么这段空隙里放的 \(l\) 中的数是固定的,不随 \(k\) 的变化而变化,简单组合计数。对于 \(r \to r\) 的空隙也一样。
对于 \(l \to r\) 的空隙 \([i, j]\),也有类似的想法。首先我们可以通过计算 \(t = i - a_i\) 知道 \(i\) 之前放了多少个 \(r\) 中的数,而 \([i, j]\) 中最多放 \(len\) 个 \(r\) 中的数,所以我们就知道合法的 \(k\) 是有一个范围的 \([a_j - t - len - 1, a_j - t - 1]\),对于 \(r \to l\) 也可以对 \(k\) 产生限制。并且这样的合法 \(k\) 的区间长度为空隙长度 \(len\),然后我们在对所有区间的 \(k\) 范围取交,这也处理每个 \(l \to r\)/\(r \to l\) 的空隙是遍历交中的所有 \(k\),用 \(n^2\) 的办法算就行。这样的空隙不超过 \(O(\frac{n}{len})\) 个,所以总的复杂度 \(O(n)\)。
Code
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 5, mod = 998244353;
typedef long long ll;
ll inv[N], fac[N], ifac[N], pw[N], f[N];
ll C(int n, int m){
if(m > n || m < 0) return 0;
return fac[n] * ifac[m] % mod * ifac[n - m] % mod;
}
void add(ll &x, ll y){ (x += y) %= mod; }
int n, a[N], b[N];
void solve(){
cin >> n;
bool fl = 0;
for(int i = 1; i <= n; ++i){
cin >> a[i];
if(a[i] != i && a[i] != -1) fl = 1;
}
if(!fl){
int lst = 0;
ll ans = 0;
for(int i = 1; i <= n; ++i){
if(a[i] == -1) continue;
int len = i - lst - 1;
add(ans, pw[len] - len - 1);
lst = i;
}
add(ans, pw[n - lst] - (n - lst) - 1);
cout << (ans + 1) % mod << '\n';
return;
}
int o = 0, lst = 0;
for(int i = 1; i <= n; ++i){
if(a[i] == -1) continue;
if(a[i] == i) b[i] = o;
else{
o = 1;
if(a[i] > i) b[i] = 1;
else b[i] = 0;
}
// cout << i << ' ' << b[i] << '\n';
}
int l = 1, r = n - 1;
for(int i = 1; i <= n; ++i){
if(a[i] == -1) continue;
if(lst){
if(b[lst] == 0 && b[i] == 1){
int mn = lst - a[lst], mx = mn + i - lst - 1;
l = max(l, a[i] - mx - 1), r = min(r, a[i] - mn - 1);
}
if(b[lst] == 1 && b[i] == 0){
int mx = i - a[i], mn = mx - (i - lst - 1); // containing lst
l = max(l, a[lst] - mx), r = min(r, a[lst] - mn);
}
// cout << i << ' ' << l << ' ' << r << '\n';
}
lst = i;
}
// cout << l << ' ' << r << '\n';s
lst = 0;
ll ans = 1;
for(int i = l; i <= r; ++i) f[i] = 1;
for(int i = 1; i <= n; ++i){
if(a[i] == -1) continue;
int cnt = i - lst - 1;
if(!lst){
if(b[i] == 0) ans = ans * C(i - 1, a[i] - 1) % mod;
else{
for(int k = l; k <= r; ++k){
f[k] = f[k] * C(i - 1, a[i] - (k + 1));
}
}
lst = i;
continue;
}
if(b[i] == b[lst]){
int need = a[i] - a[lst] - 1;
ans = ans * C(cnt, need) % mod;
}
else{
if(b[lst] == 0 && b[i] == 1){
for(int k = l; k <= r; ++k){
int p = lst - a[lst]; // p
int need = a[i] - (k + p) - 1;
// cout << p << '\n';
f[k] = f[k] * C(cnt, need) % mod;
}
}
if(b[lst] == 1 && b[i] == 0){
for(int k = l; k <= r; ++k){
int p = lst - (a[lst] - k);
int need = a[i] - p - 1;
f[k] = f[k] * C(cnt, need) % mod;
}
}
// cout << i << ' ' << f[10] << '\n';
}
lst = i;
}
// cout << f[10] << '\n';
if(lst){
if(b[lst] == 1){
for(int k = l; k <= r; ++k){
int need = n - a[lst];
f[k] = f[k] * C(n - lst, need) % mod;
}
}
else{
for(int k = l; k <= r; ++k){
int need = k - a[lst];
f[k] = f[k] * C(n - lst, need) % mod;
}
}
}
// cout << ans << '\n';
ll sum = 0;
for(int k = l; k <= r; ++k) add(sum, f[k]);
ans = ans * sum % mod;
cout << ans << '\n';
}
int main(){
cin.tie(nullptr)->sync_with_stdio(0);
pw[0] = inv[1] = fac[0] = fac[1] = ifac[0] = ifac[1] = 1;
pw[1] = 2;
for(int i = 2; i <= 1e6; ++i){
pw[i] = pw[i - 1] * 2 % mod;
inv[i] = (mod - mod / i) * inv[mod % i] % mod;
fac[i] = fac[i - 1] * i % mod;
ifac[i] = ifac[i - 1] * inv[i] % mod;
}
int T; cin >> T;
while(T--) solve();
return 0;
}

浙公网安备 33010602011771号