2023 年牛客多校第六场题解

A Tree

题意:给定 n n n 个点的一棵边带权的树,点有黑白二色( 0 , 1 0,1 0,1 表示),现在可以以 a i a_i ai 的价值翻转第 i i i 个点的颜色,一对异色点 ( u , v ) (u,v) (u,v) 的价值为树上路径的最大边权值。问经过任意颜色翻转后,价值减去代价的最大值。 1 ≤ n ≤ 3 × 1 0 3 1 \le n \le 3\times 10^3 1n3×103 1 ≤ a i ≤ 1 0 9 1 \le a_i \le 10^9 1ai109

解法:注意到点对的价值等于路径上最大边权值,不难想到 Kruskal 重构树——即根据边权从小到大的顺序枚举边,考虑加入这条边前后该边两端点所在连通块合并的贡献。显然由于边权是从小到大枚举的,因而每次尝试根据边 ( u , v , w ) (u,v,w) (u,v,w) 执行并查集合并的时候,该边两端点 u , v u,v u,v 所在连通块点集 S u , S v S_u,S_v Su,Sv 点两两之间( x ∈ S u , y ∈ S v x \in S_u,y \in S_v xSu,ySv)的价值就为该边边权 w w w

不难发现,一条边合并的贡献仅与两侧点集中黑点和白点数目有关,而与两侧点集内部黑点和白点分布无关——原点集内部黑白点分布不会经过这条新加入的边,因而不会对该边和该边边权贡献计算产生任何影响。因而可以考虑对于每个点集 S u S_u Su 维护一个 dp 数组 f S u f_{S_u} fSu,第 i i i 项表示该集合内有 i i i 个白点的最大价值(该点集内已经有的价值减去该点集内翻转代价),初始时 f S u , a u = 0 f_{S_u,a_u}=0 fSu,au=0 f S u , a u ⊕ 1 = − a i f_{S_u,a_u \oplus 1}=-a_i fSu,au1=ai 即颜色不同时要减去翻转代价。当一条边要合并时,转移是显然的:
f S u ′ , i = max ⁡ 0 ≤ k ≤ ∣ S u ∣ ∧ 0 ≤ i − k ≤ ∣ S v ∣ f S u , k + f S v , i − k + w ( k ( ∣ S v ∣ − i + k ) + ( ∣ S u ∣ − k ) ( i − k ) ) f_{S_u',i}=\max_{0 \le k \le |S_u| \land 0 \le i-k \le |S_v|} f_{S_u,k}+f_{S_v,i-k}+w(k(|S_v|-i+k)+(|S_u|-k)(i-k)) fSu,i=0kSu0ikSvmaxfSu,k+fSv,ik+w(k(Svi+k)+(Suk)(ik))
使用启发式合并就可以做到整体 O ( n 2 ) \mathcal O(n^2) O(n2) 的合并速度。

#include <bits/stdc++.h>
using namespace std;
const int N = 3000;
const long long inf = 0x3f3f3f3f3f3f3f3fll;
int father[N + 5], a[N + 5], siz[N + 5];
long long cost[N + 5];
int getfather(int x)
{
    return father[x] == x ? x : father[x] = getfather(father[x]);
}
vector<long long> f[N + 5];
void merge(int u, int v, long long w)
{
    u = getfather(u);
    v = getfather(v);
    if (u == v)
        return;
    if (siz[u] > siz[v])
        swap(u, v);
    int n = f[u].size() - 1, m = f[v].size() - 1;
    vector<long long> temp(n + m + 1);
    for (auto &x : temp)
        x = -inf;
    for (int i = 0; i <= n + m; i++)
        for (int j = 0; j <= n; j++)
            if (i - j <= m && i >= j)
            {
                int k = i - j;
                int cnt = j * (m - k) + (n - j) * k;
                temp[i] = max(temp[i], f[u][j] + f[v][k] + cnt * w);
            }
    f[v] = temp;
    f[u].clear();
    father[u] = v;
    siz[v] += siz[u];
}
struct edge
{
    int u, v;
    long long w;
    bool operator<(const edge &b) const
    {
        return w < b.w;
    }
    edge(int _u, int _v, long long _w) : u(_u), v(_v), w(_w) {}
};
int main()
{
    int n;
    scanf("%d", &n);
    for (int i = 1; i <= n; i++)
    {
        scanf("%d", &a[i]);
        father[i] = i;
        siz[i] = 1;
        f[i].resize(2);
    }
    for (int i = 1; i <= n; i++)
    {
        scanf("%lld", &cost[i]);
        f[i][a[i] ^ 1] = -cost[i];
    }
    vector<edge> q;
    for (int i = 1, u, v, w; i < n; i++)
    {
        scanf("%d%d%d", &u, &v, &w);
        q.emplace_back(u, v, w);
    }
    sort(q.begin(), q.end());
    for (auto [u, v, w] : q)
        merge(u, v, w);
    auto ans = f[getfather(1)];
    printf("%lld", *max_element(ans.begin(), ans.end()));
    return 0;
}

B Distance

题意:给定长度为 n n n 的两个序列 { a } i = 1 n \{a\}_{i=1}^n {a}i=1n { b } i = 1 n \{b\}_{i=1}^n {b}i=1n,从中选出两个大小相等的集合 S ⊆ A , T ⊆ B S \subseteq A,T \subseteq B SA,TB,每次可以选择 x ∈ S x \in S xS y ∈ T y \in T yT 并执行 x ← x + 1 x \leftarrow x+1 xx+1 x ← x − 1 x \leftarrow x-1 xx1 y ← y + 1 y \leftarrow y+1 yy+1 y ← y + 1 y \leftarrow y+1 yy+1。记将两个集合变成相同的最小操作次数为 c ( S , T ) c(S,T) c(S,T),求 ∑ S ⊆ A ∑ T ⊆ B c ( S , T )   m o d   998   244   353 \displaystyle \sum_{S \subseteq A}\sum_{T \subseteq B} c(S,T) \bmod 998\ 244\ 353 SATBc(S,T)mod998 244 353 1 ≤ n ≤ 3 × 1 0 3 1 \le n \le 3\times 10^3 1n3×103 1 ≤ a i , b i ≤ 1 0 9 1\le a_i,b_i \le 10^9 1ai,bi109

解法:显然本题不太能枚举集合去依次计算贡献,但是注意到如果两个子集 S ⊆ A , T ⊆ B S \subseteq A,T \subseteq B SA,TB 有序且能匹配,则必然是 S S S 中第 i i i 大元素 S i S_i Si 转移到 T i T_i Ti,因而可以考虑拆分算贡献——即每一对 ( a i , b j ) (a_i,b_j) (ai,bj) 如果是匹配的,那么有多少种情况让它们匹配,每种情况对答案贡献是多少(这里是显然的 ∣ a i − b j ∣ |a_i-b_j| aibj),独立去看这一对对答案的贡献。

考虑 { a } i = 1 n \{a\}_{i=1}^n {a}i=1n i i i 个数和 { b } i = 1 n \{b\}_{i=1}^n {b}i=1n j j j 个数选出相同个数的数字的方案数为 f i , j f_{i,j} fi,j,不妨令 i ≤ j i \le j ij,则有:
f i , j = ∑ k = 0 i ( i k ) ( j k ) = ∑ k = 0 i ( i i − k ) ( j k ) = ( i + j i ) \begin{aligned} f_{i,j}&=\sum_{k=0}^{i} \binom{i}{k}\binom{j}{k}\\ &=\sum_{k=0}^{i} \binom{i}{i-k}\binom{j}{k}=\binom{i+j}{i} \end{aligned} fi,j=k=0i(ki)(kj)=k=0i(iki)(kj)=(ii+j)
此处使用范德蒙德卷积公式。

此处也可以有另一种理解:考虑证明转移式 f i , j ← f i − 1 , j + f i , j − 1 f_{i,j}\leftarrow f_{i-1,j}+f_{i,j-1} fi,jfi1,j+fi,j1 的正确性。假定 f i − 1 , j − 1 , f i , j − 1 , f i − 1 , j f_{i-1,j-1},f_{i,j-1},f_{i-1,j} fi1,j1,fi,j1,fi1,j 正确,考虑它如何转移到 f i , j f_{i,j} fi,j ( i , j ) (i,j) (i,j) 同选或同不选,或者从 f i , j − 1 f_{i,j-1} fi,j1 f i − 1 , j f_{i-1,j} fi1,j 转移而来。而 f i − 1 , j f_{i-1,j} fi1,j f i , j − 1 f_{i,j-1} fi,j1 中各包含一份重复的 f i − 1 , j − 1 f_{i-1,j-1} fi1,j1,所以 f i − 1 , j f_{i-1,j} fi1,j f i , j − 1 f_{i,j-1} fi,j1 恰好完全囊括所有的情况。因而 f i , j ← f i − 1 , j + f i , j − 1 f_{i,j}\leftarrow f_{i-1,j}+f_{i,j-1} fi,jfi1,j+fi,j1 转移式正确。

因而对于 a i a_i ai b j b_j bj 匹配的情况,前后缀的方案数使用组合数公式计算即可。总复杂度 O ( n 2 ) \mathcal O(n^2) O(n2)

#include <bits/stdc++.h>
using namespace std;
const int P = 998244353, N = 2000;
long long pre[N + 5][N + 5], suf[N + 5][N + 5];
int a[N + 5], b[N + 5];
int main()
{
    int n;
    scanf("%d", &n);
    for (int i = 1; i <= n; i++)
        scanf("%d", &a[i]);
    for (int i = 1; i <= n; i++)
        scanf("%d", &b[i]);
    sort(a + 1, a + n + 1);
    sort(b + 1, b + n + 1);
    for (int i = 0; i <= n; i++)
        pre[i][0] = pre[0][i] = 1;
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= n; j++)
            pre[i][j] = (pre[i - 1][j] + pre[i][j - 1]) % P;
    for (int i = 0; i <= n + 1; i++)
        suf[i][n + 1] = suf[n + 1][i] = 1;
    for (int i = n; i >= 1; i--)
        for (int j = n; j >= 1; j--)
            suf[i][j] = (suf[i][j + 1] + suf[i + 1][j]) % P;
    long long ans = 0;
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= n; j++)
        {
            long long cur = abs(a[i] - b[j]);
            ans = (ans + cur * pre[i - 1][j - 1] % P * suf[i + 1][j + 1]) % P;
        }
    printf("%lld", ans);
    return 0;
}

C idol!!

题意:给定 n n n,求 ∏ i = 1 n i ! ! \displaystyle \prod_{i=1}^n i!! i=1ni!! 的末尾 0 0 0 个数。 1 ≤ n ≤ 1 0 18 1\le n \le 10^{18} 1n1018

解法:利用性质 ( n − 1 ) ! ! × n ! ! = n ! (n-1)!!\times n!!=n! (n1)!!×n!!=n! 将双阶乘组合一下。对于偶数,原式等价于 ∏ i = 1 n 2 ( 2 i ) ! \displaystyle \prod_{i=1}^{\frac{n}{2}} (2i)! i=12n(2i)!,而奇数等价于 ∏ i = 1 n + 1 2 ( 2 i − 1 ) ! \displaystyle \prod_{i=1}^{\frac{n+1}{2}} (2i-1)! i=12n+1(2i1)!

显然在统计阶乘末尾 0 0 0 的时候, 5 5 5 的数目会远少于 2 2 2 的数目,因而本质等价于统计含 5 5 5 因子的个数——枚举 k k k,统计有多少个数会是 5 k 5^k 5k 的倍数,对所有 k k k 求和。

首先考虑求 n ! n! n! 一项末尾 0 0 0 个数。对于一个最高含有 5 k 5^k 5k 的阶乘项 n n n(即 a r g m a x { k ∣ 5 k ∣ n } {\rm argmax}\{k|5^k|n\} argmax{k5kn}),对于 5 i 5^i 5i 次项, n ! n! n! 中至少有 ⌊ n 5 i ⌋ \left \lfloor \dfrac{n}{5^i}\right \rfloor 5in 个乘积项存在 5 i 5^i 5i i i i 5 5 5 因子,则有 ⌊ n 5 i ⌋ − ⌊ n 5 i + 1 ⌋ \left \lfloor \dfrac{n}{5^{i}}\right \rfloor-\left \lfloor \dfrac{n}{5^{i+1}}\right \rfloor 5in5i+1n 个数有且仅有 i i i 5 5 5 因子。考虑对这些求和:

i = 1 → 5 i=1 \to 5 i=15 n = 30 n=30 n=30 6 6 6 个含 5 5 5 的倍数,但是只有 5 5 5 个是恰好最高包含 5 5 5——存在 25 25 25 包含了 5 2 5^2 52
a n s = ∑ i = 1 N i ( ⌊ n 5 i ⌋ − ⌊ n 5 i + 1 ⌋ ) = ∑ i = 1 N ( i − ( i − 1 ) ) ⌊ n 5 i ⌋ − N ⌊ n 5 N + 1 ⌋ = ∑ i = 1 k ⌊ n 5 i ⌋ \begin{aligned} {\rm ans}&=\sum_{i=1}^Ni\left(\left \lfloor \dfrac{n}{5^{i}}\right \rfloor-\left \lfloor \dfrac{n}{5^{i+1}}\right \rfloor\right)\\ &=\sum_{i=1}^N(i-(i-1))\left \lfloor \dfrac{n}{5^{i}}\right \rfloor-N\left \lfloor \dfrac{n}{5^{N+1}}\right \rfloor\\ &=\displaystyle \sum_{i=1}^k \left \lfloor \dfrac{n}{5^i}\right \rfloor \end{aligned} ans=i=1Ni(5in5i+1n)=i=1N(i(i1))5inN5N+1n=i=1k5in
i = 1 i=1 i=1 n / 5 − n / 25 n/5-n/25 n/5n/25 i = 2 i=2 i=2 n / 25 − n / 125 n/25-n/125 n/25n/125

其中 N N N 充分大,满足 5 N ≥ n 5^N \ge n 5Nn,因而 ⌊ n 5 N + 1 ⌋ = 0 \left \lfloor \dfrac{n}{5^{N+1}}\right \rfloor=0 5N+1n=0。因而 n ! n! n! 一项的贡献( 5 5 5 因子数目)为 ∑ i = 1 k ⌊ n 5 i ⌋ \displaystyle \sum_{i=1}^k \left \lfloor \dfrac{n}{5^i}\right \rfloor i=1k5in

因而原式(下文以偶数为例,奇数同理转化为 2 k − 1 2k-1 2k1)等价于:
∑ i = 1 N ( ∑ j = 1 n 2 ⌊ 2 j 5 i ⌋ ) \sum_{i=1}^{N} \left(\sum_{j=1}^{\frac{n}{2}} \left \lfloor \dfrac{2j}{5^i}\right \rfloor\right) i=1N j=12n5i2j
其中 N N N 充分大,满足 5 N ≥ n 5^N \ge n 5Nn。内层括号等价于类欧形式 ∑ i = 1 n ⌊ a i + b c ⌋ \displaystyle \sum_{i=1}^{n} \left \lfloor \dfrac{ai+b}{c}\right \rfloor i=1ncai+b,可以直接套用类欧公式计算。

当然由于本例中式子较为简单,因而可以直接使用等差数列计算,一次性计算 2 × 5 k 2\times 5^k 2×5k 的范围内数字的和,这个和是等差数列。这样做复杂度为 O ( log ⁡ n ) \mathcal O(\log n) O(logn)

#include <bits/stdc++.h>
#define fp(i, a, b) for (int i = a, i##_ = b; i <= i##_; ++i)
#define fd(i, a, b) for (int i = a, i##_ = b; i >= i##_; --i)

using namespace std;
using ll = long long;
using i128 = __int128_t;
ll n; i128 c;
void print(i128 x) {
    if (!x) return;
    print(x / 10);
    putchar(x % 10 + '0');
}
void Solve() {
    scanf("%lld", &n);
    if (n & 1) {
        ll k = n + 1;
        while (k) c += k / 5, k /= 5;
        k = (n + 1) / 2;
        while (k) c -= k / 5, k /= 5;
        --n;
    }
    auto S2 = [](i128 x) { return x * (x + 1) / 2; };
    for (i128 k = 5; k <= n; k *= 5)
        c += k / 2 * S2(n / k - 1) + 2 * S2(n / k / 2) + n / k * ((n % k + 1) / 2);
    if (!c) puts("0");
    else print(c), puts("");
}
int main() {
    int t = 1;
    // scanf("%d", &t);
    while (t--) Solve();
    return 0;
}

E Sequence

题意:给定长度为 n n n 的序列 { a } i = 1 n \{a\}_{i=1}^n {a}i=1n q q q 次询问区间 [ l , r ] [l,r] [l,r] 上是否可以找到长度为 k + 1 k+1 k+1 数列 { b } i = 0 k \{b\}_{i=0}^k {b}i=0k 满足 l = b 0 ≤ b 1 < b 2 < ⋯ b k − 1 < r = b k l =b_0\le b_1 <b_2 <\cdots b_{k-1} <r=b_k l=b0b1<b2<bk1<r=bk ∀ i ∈ [ 0 , k − 1 ] \forall i \in [0,k-1] i[0,k1] ∑ b i + 1 b i + 1 a i \displaystyle \sum_{b_i+1}^{b_{i+1}}a_i bi+1bi+1ai 均为偶数。多测, 1 ≤ T ≤ 1 0 4 1 \le T \le 10^4 1T104 ∑ n , ∑ q ≤ 1 0 5 \sum n,\sum q \le 10^5 n,q105 1 ≤ a i ≤ 1 0 10 1 \le a_i \le 10^{10} 1ai1010

解法:显然 ∑ i = l r a i \displaystyle \sum_{i=l}^r a_i i=lrai 必为偶数,否则无解。

考虑前缀和的奇偶性。显然对于前缀和数组 s s s 而言, s l − 1 , s b 1 , s b 2 , ⋯   , s r s_{l-1},s_{b_1},s_{b_2},\cdots,s_r sl1,sb1,sb2,,sr 的奇偶性全部相同。因而只需要前缀和处理前缀和数组的奇偶性,统计 [ l − 1 , r ] [l-1,r] [l1,r] 区间上和 s l − 1 s_{l-1} sl1 奇偶性相同的 s x s_x sx 个数即可。复杂度 O ( ∑ n + ∑ q ) \mathcal O\left(\sum n+\sum q\right) O(n+q)

#include <bits/stdc++.h>
#define fp(i, a, b) for (int i = a, i##_ = int(b); i <= i##_; ++i)
#define fd(i, a, b) for (int i = a, i##_ = int(b); i >= i##_; --i)

using namespace std;
using ll = long long;
const int N = 1e5 + 5;
int n, q, sum[N], cnt[N][2];
void Clear() {
    fp(i, 1, n) sum[i] = cnt[i][0] = cnt[i][1] = 0;
}
void Solve() {
    scanf("%d%d", &n, &q);
    ll x;
    fp(i, 1, n) {
        scanf("%lld", &x);
        sum[i] = sum[i - 1] ^ (x & 1);
        cnt[i][0] = cnt[i - 1][0] + (sum[i] == 0);
        cnt[i][1] = cnt[i - 1][1] + (sum[i] == 1);
    }
    int l, r, k;
    while (q--) {
        scanf("%d%d%d", &l, &r, &k);
        if (sum[r] != sum[l - 1]) puts("NO");
        else if (cnt[r][sum[r]] - cnt[l - 1][sum[r]] >= k)
            puts("YES");
        else
            puts("NO");
    }
}
int main() {
    int t = 1;
    scanf("%d", &t);
    while (t--) Solve();
    return 0;
}

G Gcd

题意:初始时集合 S S S 内有两个数 x , y x, y x,y,一次操作中可以选取两个数 a , b a, b a,b a − b a - b ab gcd ⁡ ( ∣ a ∣ , ∣ b ∣ ) \gcd(|a|, |b|) gcd(a,b) 插入集合。问最后能否使得元素 z z z 在集合中。 0 ≤ x , y , z ≤ 1 0 9 0 \le x,y,z \le 10^9 0x,y,z109

解法:不难注意到这个操作就是辗转相减,因而可以得到 gcd ⁡ ( a , b ) \gcd(a,b) gcd(a,b)。这时所有 gcd ⁡ ( a , b ) \gcd(a,b) gcd(a,b) 的倍数(因为可以产生负数)就都可以得到了。注意特判 z = 0 z=0 z=0 的情况。

#include <bits/stdc++.h>

using namespace std;
void Solve() {
    int a, b, c, d = 0;
    scanf("%d%d%d", &a, &b, &c);
    puts((!c && (!a || !b)) || (c && c % __gcd(a, b) == 0) ? "YES" : "NO");
}
int main() {
    int t = 1;
    scanf("%d", &t);
    while (t--) Solve();
    return 0;
}

H traffic

题意:给定一个 n n n 个点 n + 1 n + 1 n+1 条边的无向连通图,每条边边权是一个关于 t t t 的一次函数 a i t + b i a_it+b_i ait+bi,要求分别算出 t = 0 , 1 , 2 , 3 , ⋯   , T t = 0, 1, 2, 3,\cdots, T t=0,1,2,3,,T 时图的最小生成树的边权和。 1 ≤ n ≤ 1 0 5 1\le n \le 10^5 1n105 0 ≤ a i , b i ≤ 1 0 8 0 \le a_i,b_i \le 10^8 0ai,bi108

解法: n n n 个点 n + 1 n+1 n+1 条边的图存在两个环,现在需要保留其中一个生成树。那么可能存在以下两种情况:

在这里插入图片描述

对于两个独立环,那么最大去除两条边等价于每一个环去除一条边。

在这里插入图片描述

对于嵌环(两个环有公共边),那么这时图上共有三个本质不同的环和三条半环链(图中 2 → 1 → 5 → 6 , 2 → 7 → 6 , 2 → 3 → 4 → 6 2 \to 1 \to 5 \to6,2 \to 7\to 6,2 \to 3\to 4 \to 6 2156,276,2346)。这时三条半环边上任取两条半环链,每条半环链上去除一条边。

因而问题转化为 [ 0 , T ] [0,T] [0,T] 上查询若干个一次函数(半环链上的边权值)的最大值。这个问题可以使用李超树直接解决。当然本题由于都是对全域 [ 0 , T ] [0,T] [0,T] 上操作,因而单调队列维护这一凸包或者半平面交都是可以的。

问题转化为如何快速找环。这里可以考虑使用并查集,依次枚举边并尝试合并,如果边上两点已经联通,则说明发现了环,将返祖边该两点颜色进行染色。等所有边处理完成后,再进行一次 dfs 将颜色进行传播。不难发现如果点在链上,则根据数学归纳法得到异或出来的值必然为 0 0 0,而返祖边所在的环上的每个点都会染上返祖边的颜色。这时点上颜色就和边上颜色相同。如果颜色使用恰当(如 2 k 2^k 2k),则可以快速找到每个点和每条边在哪些环上。

#include <bits/stdc++.h>
#define fp(i, a, b) for (int i = a, i##_ = b; i <= i##_; ++i)
#define fd(i, a, b) for (int i = a, i##_ = b; i >= i##_; --i)

using namespace std;
using ll = long long;
const int N = 1e5 + 5;
// 李超树
class LC_SegT {
    struct Func {
        ll k, b;
        Func(ll k = 0, ll b = 0) : k(k), b(b) {}
        ll operator()(const ll x) const { return k * x + b; }
    } tr[N << 2];
#define lc (p << 1)
#define rc (p << 1 | 1)
    void insert(int p, int L, int R, int a, int b, Func k) {
        int m = (L + R) >> 1;
        if (a <= L && R <= b) {
            Func &F = tr[p];
            int res = (k(L) > F(L)) + (k(R) > F(R));
            if (res == 2) F = k;
            else if (res) {
                if (k(m) > F(m)) swap(F, k);
                if (k(L) > F(L)) insert(lc, L, m, a, b, k);
                else insert(rc, m + 1, R, a, b, k);
            }
            return;
        }
        if (a <= m) insert(lc, L, m, a, b, k);
        if (b > m) insert(rc, m + 1, R, a, b, k);
    }
#undef lc
#undef rc
  public:
    int n;
    void init(const int Lim) { for (n = 1; n < Lim; n <<= 1); }
    void insert(Func k) { insert(1, 1, n, 1, n, k); }
    ll qry(int x) {
        ll res = 0;
        for (int p = x + n - 1; p; p >>= 1)
            res = max(res, tr[p](x));
        return res;
    }
} T[3];
int n, m, q, dft, fa[N], id[N], tg[N], dfn[N];
ll sA, sB;
struct Edge { int a, b; };
vector<Edge> E;
vector<int> C[5];
map<pair<int, int>, int> mp;
vector<pair<int, int>> G[N], stk;
int GF(int x) { return fa[x] == x ? x : fa[x] = GF(fa[x]); }
int merge(int x, int y) { return x = GF(x), y = GF(y), fa[x] = y, x != y; }
void dfs(int u, int p) {
    // u->v的边有边编号e
    for (auto [v, e] : G[u]) {
        if (e == p) continue;
        dfs(v, e), id[u] ^= id[v];
    }
    if (p >= 0) tg[p] = id[u];
}
void Solve() {
    scanf("%d%d", &n, &q), m = 1;
    fp(i, 1, n) fa[i] = i;
    for (int i = 0, u, v, a, b; i <= n; ++i) {
        scanf("%d%d%d%d", &u, &v, &a, &b);
        sA += a, sB += b, E.push_back({a, b});
        if (merge(u, v))
            G[u].push_back({v, i}), G[v].push_back({u, i});
        else
            id[u] ^= m, id[v] ^= m, tg[i] = m, m <<= 1;        
    }
    dfs(1, -1);
    fp(i, 0, n) if (tg[i]) C[tg[i] - 1].push_back(i);
    fp(i, 0, 2) {
        T[i].init(q + 1);
        for (auto x : C[i])
            T[i].insert({E[x].a, E[x].b - E[x].a});
    }
    fp(i, 1, q + 1) {
        vector<ll> val(3);
        fp(j, 0, 2) val[j] = T[j].qry(i);
        sort(val.begin(), val.end());
        printf("%lld\n", sB - val[1] - val[2]);
        sB += sA;
    }
}
int main() {
    int t = 1;
    // scanf("%d", &t);
    while (t--) Solve();
    return 0;
}

J Even

题意:给定一个长度为 n n n 的序列 { a } \{a\} {a}。对于一个区间 [ l , r ] [l,r] [l,r],在一次操作中,若区间内有正偶数,则令区间中偶数的最大值 x ← x 2 x\leftarrow \dfrac{x}{2} x2x,否则令最大值 x ← x − 1 2 x \leftarrow \dfrac{x-1}{2} x2x1 q q q 次询问,求对一个区间操作 k k k 次以后,区间最大值。 1 ≤ n ≤ 1 0 4 1 \le n \le 10^4 1n104 1 ≤ q ≤ 1 0 5 1\le q \le 10^5 1q105 1 ≤ a i , k ≤ 1 0 9 1 \le a_i,k \le 10^9 1ai,k109

解法:下面考虑一个区间 [ l , r ] [l,r] [l,r] 中如何处理。在一个区间中,如果存在偶数,则必定是先将偶数部分全部除干净再开始处理奇数。因而在有奇有偶的情况下,可以先把偶数单独拎出来。

不妨设这个区间中偶数分别为 { p 1 × 2 k 1 , p 2 × 2 k 2 , ⋯   , p m × 2 k m } \{p_1\times 2^{k_1},p_2\times 2^{k_2},\cdots,p_m\times 2^{k_m}\} {p1×2k1,p2×2k2,,pm×2km},其中 p 1 , p 2 , ⋯   , p k p_1,p_2,\cdots,p_k p1,p2,,pk 均为奇数,则可以考虑维护可重集 { { p 1 × 2 k 1 , p 1 × 2 k 1 − 1 , ⋯   , p 1 } , { p 2 × 2 k 2 , p 2 × 2 k 2 − 1 , ⋯   , p 2 } , ⋯   , { p m × 2 k m , ⋯   , p m } } \{\{p_1\times 2^{k_1},p_1\times 2^{k_1-1},\cdots,p_1\},\{p_2\times 2^{k_2},p_2\times 2^{k_2-1},\cdots,p_2\},\cdots,\{p_m\times 2^{k_m},\cdots,p_m\}\} {{p1×2k1,p1×2k11,,p1},{p2×2k2,p2×2k21,,p2},,{pm×2km,,pm}} 的第 k k k 大——一次操作处理的是一个最大值,并将它删掉。进行 k k k 次操作等价于新序列的第 k + 1 k+1 k+1 大(即尚未处理的数字)。

如果整个区间都是奇数,则处理一次之后如果变成偶数就会一直处理它直到整个序列再次恢复全部奇数的情况。假设对于奇数 a i a_i ai,它的处理链为 a i → a i , 1 → a i , 2 → a i , k a_i \to a_{i,1} \to a_{i,2} \to a_{i,k} aiai,1ai,2ai,k,其中 a i , a i , k a_i,a_{i,k} ai,ai,k 为奇数, a i , 1 , a i , 2 , ⋯   , a i , k − 1 a_{i,1},a_{i,2},\cdots,a_{i,k-1} ai,1,ai,2,,ai,k1 为偶数。则对于区间 { a 1 , a 2 , ⋯   , a l } \{a_1,a_2,\cdots,a_l\} {a1,a2,,al} 可以考虑维护新序列 { { a 1 , a 1 , a 1 , ⋯   , a 1 , a 1 , k 1 } , { a 2 , a 2 , a 2 , ⋯   , a 2 , a 2 , k 2 } , ⋯   , { a l , a l , a l , ⋯   , a l , a l , k l } } \{\{a_1,a_{1},a_{1},\cdots,a_1,a_{1,k_1}\},\{a_2,a_2,a_2,\cdots,a_2,a_{2,k_2}\},\cdots,\{a_l,a_l,a_l,\cdots,a_l,a_{l,k_l}\}\} {{a1,a1,a1,,a1,a1,k1},{a2,a2,a2,,a2,a2,k2},,{al,al,al,,al,al,kl}}。注意下标就是 a i a_i ai 不是 a i , j a_{i,j} ai,j,因为出现了偶数优先处理偶数,等价于在选取最大值的过程中仍然是原数字进行操作。因而处理 k k k 次操作仍然等价于新序列的第 k + 1 k+1 k+1 大。由于每个数字只会操作 O ( log ⁡ V ) O(\log V) O(logV) 轮,因而整个新序列长度仅有 O ( n log ⁡ V ) O(n \log V) O(nlogV),使用主席树维护区间第 k k k 大的复杂度为 O ( log ⁡ 2 n ) O(\log^2n) O(log2n),因而总复杂度 O ( q log ⁡ V log ⁡ 2 ( n log ⁡ V ) ) \mathcal O(q \log V \log^2(n \log V)) O(qlogVlog2(nlogV))

#include <bits/stdc++.h>
#define fp(i, a, b) for (int i = a, i##_ = b; i <= i##_; ++i)
#define fd(i, a, b) for (int i = a, i##_ = b; i >= i##_; --i)

using namespace std;
using ll = long long;
const int N = 1e4 + 5, M = 1 << 30;

struct Node {
    Node *l, *r; int s;
    Node(Node *t) { if (t) *this = *t; }
};
int n, q, cnt, res;
vector<Node *>t0, t1;
Node *add(Node *t, int l, int r, int x, int v) {
    t = new Node(t);
    t->s += v;
    if (r == l + 1) return t;
    int m = (l + r) >> 1;
    if (x < m) t->l = add(t->l, l, m, x, v);
    else t->r = add(t->r, m, r, x, v);
    return t;
}
int qry(Node *t1, Node *t2, int l, int r, int k) {
    if (r == l + 1) {
        if (l & 1) cnt = t1->s - t2->s, res = k;
        return l;
    }
    int s = t1->r->s - t2->r->s, m = (l + r) >> 1;
    if (s > k) return qry(t1->r, t2->r, m, r, k);
    return qry(t1->l, t2->l, l, m, k - s);
}
void Solve() {
    scanf("%d%d", &n, &q);
    Node *z = new Node({});
    z->l = z->r = z;
    t0.resize(n + 1), t0[0] = z, t1 = t0;
    for (int i = 1, x; i <= n; ++i){
        t0[i] = t0[i - 1], t1[i] = t1[i - 1];
        scanf("%d", &x);
        while (x % 2 == 0) t0[i] = add(t0[i], 0, M, x, 1), x /= 2;
        while (x) {
            int t = x == 1 ? 1 : __builtin_ctz(x / 2) + 1;
            t1[i] = add(t1[i], 0, M, x, t), x >>= t;
        }
    }
    for (int l, r, k, sum, ans; q--;) {
        scanf("%d%d%d", &l, &r, &k), --l;
        sum = t0[r]->s - t0[l]->s;
        if (k <= sum)
            ans = max(qry(t0[r], t0[l], 0, M, k), qry(t1[r], t1[l], 0, M, 0));
        else {
            k -= sum;
            int x = qry(t1[r], t1[l], 0, M, k),
                c = x == 1 ? 1 : __builtin_ctz(x / 2) + 1;
            if (res <= cnt - c) ans = x;
            else ans = max(x >> (res - cnt + c), qry(t1[r], t1[l], 0, M, k - res + cnt));
        }
        printf("%d\n", ans);
    }
}
int main() {
    int t = 1;
    // scanf("%d", &t);
    while (t--) Solve();
    return 0;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值