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;
}
posted @ 2025-11-23 11:36  Hengsber  阅读(3)  评论(0)    收藏  举报