10 月 15 日模拟赛总结

Before

本文章在洛谷博客同步发布

Contest-Link

预期 \(30 + 0 + 0 + 20 = 50\)

实际 \(30 + 0 +100+ 60 = 190\)

挂分 \(-140\)

rk6,行。

开题首先瞄了眼 T1,想 dp 感觉挺玄乎,写了个暴力,跳 T2 发现什么鬼题面,直接跳 T3,感觉 T3 可做,维护区间 \(1\) 的个数不直接用线段树?写了个线段树对拍没过!非常愤怒。非常愤怒。非常愤怒。结果是暴力写错了哈哈,然后手推了一下发现没啥问题,看 T4,\(n \le 200\) 我直接打表,发现时间不够了写了个爆搜跑路。

然后就寄了。

T1

Description

\(\max^n_{i=1}\{\max^i_{j=1}a_i\times\min^i_{j=1}a_i\times i\}\)

\(1 \le n\le 2\times 10^6,1\le a_i \le 10^9\)

Solution

显然,我们需要枚举最小值找最大值,然后这一部分可以用一个神奇的思路:用并查集贪心。大致是按降序枚举每一条边,并查集维护连通块来算贡献。

具体来说,我们将 \(a\) 抽象成一条 \(n+1\) 个点,\(n\) 条边的链,每条边的边权为 \(a_i\),同时建一个并查集,记录每个分开的数列的大小。

我们考虑要在这个上面找到一个最大的区间使得当前枚举的最小值仍然等于区间的最小值,简单来说就是找到一个最大的区间使得最小值不变。由于区间最大,答案也相应在最小值不变的情况下最大化了。

考虑按照权大小从大到小选择,对于一个点,被选择时所在连通块就表示它的合法最大区间,最后并查集上维护最大值和区间大小即可。

为什么这样贪心一定是对的呢?

因为我们按权降序排序,选择一个边时,过这条边的每条路径的最小边权都由这条边贡献

好像正解是 ST 表 + 单调栈,听起来很高级的样子。

考场想法:暴力。

考场寄因:暴力。

时间复杂度 \(O(n \log n)\),空间复杂度 \(O(n)\)

Code

\(\text{100pts:}\)

// 2023/10/15 PikachuQAQ

#include <iostream>
#include <algorithm>

using namespace std;

typedef __int128 B;
typedef long long ll;

const int kMaxN = 2e6 + 7;

struct S { 
    int id;
    ll res;
    friend bool operator < (const S& a, const S& b) {
        return a.res > b.res;
    }
} b[kMaxN];

ll n, a[kMaxN];
B ans, maxn[kMaxN];
int fa[kMaxN], siz[kMaxN];
bool vis[kMaxN];

int findfa(int x) {
    return (fa[x] == x) ? x : (fa[x] = findfa(fa[x]));
}

void uni(int x) {
    vis[x] = 1;
    for (int i = -1, u, v; i <= 1; i += 2) {
        if (vis[x + i]) {
            u = findfa(x), v = findfa(x + i);
            maxn[u] = max(maxn[u], maxn[v]);
            fa[v] = u, siz[u] += siz[v];
        }
    }
}

void init() {
    for (int i = 1; i <= n; i++) {
        fa[i] = i, siz[i] = 1;
        maxn[i] = b[i].res = a[i], b[i].id = i;
    }
}

void write(B x) {
    if (x == 0) {
        return;
    }
    write(x / 10), putchar(x % 10 + 48);
}

int main() {
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    cin >> n;
    if (n == 1) {
        int x;
        cin >> x;
        cout << x;
        return 0;
    }
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
    }
    init(), sort(b + 1, b + n + 1);
    for (int i = 1, x = 0; i <= n; i = x, i++) {
        x = i, uni(b[i].id);
        while (b[i].res == b[x + 1].res) {
            x++, uni(b[x].id);
        }
        for (int j = i, p; j <= x; j++) {
            p = findfa(b[j].id), ans = max(ans, (B)b[j].res * siz[p]);
        }
    }
    write(ans);

    return 0;
}

T2

Description

给定正整数 \(n\),计算 \(n\) 个元素的集合 \(\{1, 2, \cdots, n\}\),所有非空子集的乘积取模 \(998244353\) 后的结果。

\(1 \le n \le 200\)

Solution

定义函数 \(g(i)\) 为求子集每种和的方案数。

注意到 \(1 \le n \le 200\),发现这里比较像 01 背包的模型:将 \(a_i\) 看作物品,花费为 \(i\),价值为 \(f_{j-i}\)。于是我们可以跑背包。

则答案是:

\[\prod^{n(n+1)}_{i=1}i^{g(i)} \]

考虑质数取模,根据费马小定理,有 \(S\equiv \text{ans}(\bmod \text{ }p-1)\),其中 \(S\) 为答案式子,\(\text{ans}\) 为答案,\(p\) 为取模的质数。

考场想法:爆搜。

考场寄因:爆搜。

时间复杂度 \(O(n^3)\),空间复杂度 \(O(n^2)\)

Code

\(\text{100pts:}\)

// 2023/10/15 PikachuQAQ

#include <iostream>

using namespace std;

typedef long long ll;

const int kMaxN = 60007, mod = 998244353;

ll f[kMaxN], n, ans = 1;

ll qpow(ll a, ll b, ll mod) {
    ll res;
    for (res = 1; b; a = a * a % mod, b >>= 1) {
        if (b & 1) {
            res = res * a % mod;
        }
    }
	return res % mod;
}

int main() {
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
	cin >> n, f[0] = 1;
    for (int i = 1; i <= n; i++) {
        for (int j = i * (i + 1) / 2; j >= i; j--) {
            f[j] += f[j - i] % (mod - 1);
        }
    }
    for (int i = 1; i <= n * (n + 1) / 2; i++) {
        ans *= qpow(i, f[i], mod), ans %= mod;
    }
    cout << ans % mod;

    return 0;
} 

T3

Description

长度为 \(n\) 的只包含 \(0\)\(1\) 的序列,你可以对它进行多次操作。在每次操作中,你都可以选择一个数字 \(0\) 变成 \(1\),或者选择一个数字 \(1\) 变成 \(0\),使得最终局面满足如下特点:对于任意相邻的两个 \(1\).它们在序列中的距离都是 \(d\) 的倍数,给定 \(d\),求最小操作次数。长度为 \(n\) 的只包含 \(0\)\(1\) 的序列,每次操作选一个 \(0\)\(1\) 或者 \(1\)\(0\),使得最终局面的相邻的两个 \(1\) 之间距离是 \(d\) 的倍数,问最小操作次数。

\(1 \le n \le 2\times 10^5\)

Solution

对于第一个操作,可以证明只有第二个操作(\(1\)\(0\))是最优的,第一个操作(\(0\)\(1\))用于将数列置为 \(0\),只会有一种方案,最后计算用到。

首先,题面说到距离可以是 \(d\) 的倍数,这里我们一定选 \(d\),因为当距离越大,\([i,i+d]\)\(1\) 的个数就会更多,相对于更小的 \(d\) 来说并不利。

然后,我们枚举起始位置 \([1, d]\),然后枚举 \(i-d+1\)\(i-1\),也就是不是 \(d\) 倍数的区间,找其中 \(1\) 需要置为 \(0\) 的个数,这一部分可以用线段树(不是可以直接前缀和吗?)维护,然后把所有不是 \(d\) 倍数的位置上的 \(1\) 的贡献(就是 \(1\))加起来即可。

最后和之前说的将数列置为全 \(0\) 的贡献取 \(\min\) 即可。

考场想法:线段树。

考场寄因:没寄。

时间复杂度 \(O(n\log n)\),空间复杂度 \(O(n)\)

Code

\(\text{100pts:}\)

// 2023/10/15 PikachuQAQ

#include <iostream>

using namespace std;

const int kMaxN = 2e5 + 7, kMaxM = 8e5 + 7;

typedef long long ll;

ll seg[kMaxM], add[kMaxM];
int n, d, a[kMaxN], ans, res;

int M(int l, int r) { return l + r >> 1; } 
int L(int p) { return p << 1; } 
int R(int p) { return p << 1 | 1; } 
void tag(int l, int r, int p, ll d) { add[p] += d, seg[p] += (r - l + 1) * d; } 
void pushup(int p) { seg[p] = seg[L(p)] + seg[R(p)]; }
void pushdown(int l, int r, int p) { 
    tag(l, M(l, r), L(p), add[p]), tag(M(l, r) + 1, r, R(p), add[p]);
    add[p] = 0;
}

void Build(int l, int r, int p) {
    if (l == r) {
        seg[p] = a[l];
    } else {
        Build(l, M(l, r), L(p)), Build(M(l, r) + 1, r, R(p)), pushup(p);
    }
}

void Update(int s, int t, int l, int r, int p, ll d) {
    if (s <= l && r <= t) {
        tag(l, r, p, d);
    } else {
        pushdown(l, r, p);
        if (s <= M(l, r)) {
            Update(s, t, l, M(l, r), L(p), d);
        }
        if (t >= M(l, r) + 1) {
            Update(s, t, M(l, r) + 1, r, R(p), d);
        }
        pushup(p);        
    }
}


ll Query(int s, int t, int l, int r, int p) {
    ll res = 0;
    if (s <= l && r <= t) {
        return seg[p];
    }
    pushdown(l, r, p);
    if (s <= M(l, r)) {
        res += Query(s, t, l, M(l, r), L(p));
    } 
    if (t >= M(l, r) + 1) {
        res += Query(s, t, M(l, r) + 1, r, R(p));
    }
    return res;
}

int main() {
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    cin >> n >> d;
    Build(0, n + d, 1);
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
        ans += a[i];
        Update(i, i, 0, n + d, 1, a[i]);
    }
    if (ans == 0) {
        cout << 0 << '\n';
        return 0;
    }
    ans--;
    for (int i = 1; i <= d; i++) {
        for (int j = i, x = 0; j <= n + d; j += d) {
            x = max(0, j - d + 1);
            res += Query(x, j - 1, 0, n + d, 1);
        }
        ans = min(ans, res), res = 0;
    }
    cout << ans; 

    return 0;
}

T4

Description

啊?

Solution

啊?

Code

啊?

Summary

需要掌握的:?

posted @ 2023-10-15 23:14  PikachuQAQ  阅读(22)  评论(0)    收藏  举报