首页 > 其他分享 >Good Bye 2024

Good Bye 2024

时间:2024-12-29 12:41:08浏览次数:6  
标签:Good frac int auto cin 偏移量 2024 区间 Bye

省流版
  • A. 考虑存在相邻两个数组成三角形即可
  • B. 仅考虑唯一取值的元素是否占满了当前元素的所有取值
  • C. 分阶段考虑贡献,每阶段长度减半,贡献是中点值*区间数量+总偏移量和,维护总偏移量
  • D. 最大值取于俩数组从小到大排序。对于操作,等价于修改有序数组的最右边的数,维护答案
  • E. 两种必胜情况,一种是q在叶子上,p不在叶子上,另一种是q的邻居能一步到叶子,考虑p的取值,统计非叶子非好点的数量即可

A - Tender Carpenter (cf2053 A)

题目大意

给定一个长度为 \(n\) 的数组,问是否存在多种切分方法,使得切分出的若干个连续子数组的每一个数组,任取三个数(可以是同一个),它们都能组成一个三角形。

解题思路

一个最朴素的切分方法就是每个元素为一组,这样显然是满足条件的。

考虑还有没有其他的切分方法。朴素的想法是考虑两个相邻的元素,如果两个较小值大于较大值,那么这两个元素可以组成一个三角形,那么这两个元素就可以切分到一组中。

这样就得到另一个切分方法,因此就存在多种切分方法了。否则就不存在了。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;

int main(void) {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int t;
    cin >> t;
    while (t--) {
        int n;
        cin >> n;
        vector<int> a(n);
        for (auto& i : a)
            cin >> i;
        bool ok = false;
        for (int i = 0; i < n - 1; ++i) {
            int minn = min(a[i], a[i + 1]);
            int maxx = max(a[i], a[i + 1]);
            ok |= (minn + minn > maxx);
        }
        if (ok)
            cout << "YES" << '\n';
        else
            cout << "NO" << '\n';
    }

    return 0;
}


B - Outstanding Impressionist (cf2053 B)

题目大意

有一 \(n\) 个元素的数组,但你只记得第 \(i\) 个元素的值的范围是 \([l_i,r_i]\)。

现在对于每个元素,是否存在一种情况,它的值是独一无二的,即其他元素的值都不等于它。

解题思路

考虑当前元素 \(a_i\),它的范围是 \([l_i,r_i]\),如果它不是独一无二的,那么 \([l_i,r_i]\) 中的每个数在其他位置都一定出现了。

而其他位置的元素的范围是 \([l_j,r_j]\),如果 \(l_j == r_j\),那么该位置只能取一个值,否则它可以避免和 \(a_i\)取同样的值。

因此,我们只关注那些只能取一个值(\(l_j == r_j\))的元素,并记录一下\(l_j\)这个值出现了,记 \(forbid[l_j] = 1\)。

然后对于 \(a_i\),如果 \([l_i,r_i]\) 中的每个数都在之前出现过(\(\sum_{j=l_i}^{r_i} forbid_j == r_i - l_i + 1\)),那么 \(a_i\) 就不是独一无二的。用前缀和即可\(O(1)\)判断上述条件。

注意要消除 \(a_i\) 本身的影响,即若 \(l_i == r_i\),那么要去除它对 \(forbid[l_i]\) 的贡献。

这里就需要记录两个数组:\(cnt\) 和 \(forbid\),前者记录每个值出现的次数,后者记录当前值是否出现,即 \(forbid[i] = (cnt[i] > 0)\),前缀和维护 \(forbid\) 数组。注意去除贡献时不需要修改前缀和数组。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;

int main(void) {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int t;
    cin >> t;
    while (t--) {
        int n;
        cin >> n;
        vector<int> forbid(2 * n + 1);
        vector<int> cnt(2 * n + 1);
        vector<array<int, 2>> a(n);
        auto add = [&](int x) {
            if (cnt[x] == 0)
                forbid[x] = 1;
            cnt[x]++;
        };
        auto remove = [&](int x) {
            cnt[x]--;
            if (cnt[x] == 0)
                forbid[x] = 0;
        };
        for (auto& [l, r] : a) {
            cin >> l >> r;
            if (l == r) {
                add(l);
            }
        }
        vector<int> presum(2 * n + 1);
        partial_sum(forbid.begin(), forbid.end(), presum.begin());
        auto get_sum = [&](int l, int r) {
            return presum[r] - (l == 0 ? 0 : presum[l - 1]);
        };
        string s(n, '0');
        for (int i = 0; i < n; ++i) {
            auto [l, r] = a[i];
            if (l == r) {
                remove(l);
            }
            auto sum = get_sum(l, r);
            if (sum != (r - l + 1) || forbid[l] == 0)
                s[i] = '1';
            if (l == r)
                add(l);
        }
        cout << s << '\n';
    }

    return 0;
}


C - Bewitching Stargazer (cf2053 C)

题目大意

一个长度为 \(n\) 的全排列,\(1,2,3,\dotsc,n\),给定 \(k\),对区间 \([1,n]\) 进行以下操作。

考虑当前区间\([l,r]\),长度\(len = r - l + 1\)

  • 若 \(len < k\),停止。
  • 若 \(len\) 为奇数,幸运值增加\(m = \lfloor \frac{l + r}{2} \rfloor\),考虑区间 \([l, m - 1]\) 和 \([m + 1, r]\)。
  • 若 \(len\) 为偶数,考虑区间 \([l, m]\) 和 \([m + 1, r]\)。

问最终的幸运值是多少。初始时幸运值为 \(0\)。

解题思路

  • 第一阶段,会考虑一个区间:\([1,n]\)。
  • 第二阶段,会考虑两个区间:\([1,\frac{n}{2}]\) 和 \([\frac{n}{2} + 1,n]\)。
  • 第三阶段,会考虑四个区间:\([1,\frac{n}{4}]\)、\([\frac{n}{4} + 1,\frac{n}{2}]\)、\([\frac{n}{2} + 1,\frac{3n}{4}]\)、\([\frac{3n}{4} + 1,n]\)。
  • 第四阶段,会考虑八个区间:\([1,\frac{n}{8}]\)、\([\frac{n}{8} + 1,\frac{n}{4}]\)、\([\frac{n}{4} + 1,\frac{3n}{8}]\)、\([\frac{3n}{8} + 1,\frac{n}{2}]\)、\([\frac{n}{2} + 1,\frac{5n}{8}]\)、\([\frac{5n}{8} + 1,\frac{3n}{4}]\)、\([\frac{3n}{4} + 1,\frac{7n}{8}]\)、\([\frac{7n}{8} + 1,n]\)。
  • ......(上述可能会因为奇数的原因有所偏差)
  • 区间长度 \(< k\),停止。

最终的幸运值就是每个阶段的贡献和。

注意到,无论是哪个阶段,所考虑的区间的长度都是一样的:从 \(n\),变成了 \(\frac{n}{2}\),再变成了 \(\frac{n}{4}\),再变成了 \(\frac{n}{8}\)...,这意味着,如果当前阶段的区间长度是奇数,那么它们对幸运值都有贡献,区别只是偏移量不同。

即如果第二阶段的区间长度是奇数,那么第二阶段的两个区间都会对幸运值有贡献,区别只是偏移量不同:第一个区间的贡献是 \(m\),第二个区间的贡献则是 \(shift + m\)。而这个偏移量 \(shift\) 是来自第一阶段的中点。

从中点值、偏移量的角度考虑每个阶段的贡献部分,则可以分成两个部分:中点值 \(\times\) 区间数量 + 总偏移量和。

对于当前的第 \(i\) 阶段,区间长度是 \(n\),则中点值是 \(\frac{n + 1}{2}\),区间数量是 \(2^{i - 1}\),考虑总偏移量和怎么求,从上述举例的分析可以得知它可以从上一阶段的总偏移量和中得到。

第 \(i - 1\) 阶段的总偏移量和是 \(add[i - 1]\),考虑每个偏移量的来源,像上述的第二阶段中的 \(shift\),它作用在区间\([\frac{n}{2} + 1,n]\)上,到了第三阶段,该区间就分成两个区间,每个区间算贡献时都要带上这个偏移量,因此该偏移量的贡献就会翻倍。由此实际上每个偏移量的贡献都会翻倍,即 \(add[i] = 2 \times add[i - 1]\)。

同时还有新的偏移量出现:考虑区间\([1,\frac{n}{2}]\),到了第 \(i\) 阶段,该区间会被一分为二,第二个区间就新增偏移量 \(\frac{n}{2}\),而一共有 \(2^{i - 1}\) 个区间,因此新增的偏移量和就是 \(2^{i - 1} \times \frac{n}{2}\)。

因此,从第 \(i - 1\) 阶段到第 \(i\) 阶段,总偏移量和就是 \(add[i] = 2 \times add[i - 1] + 2^{i - 1} \times \frac{n}{2}\)。注意这里的\(n\)是当前阶段的区间长度,不是题目给定的\(n\)。

由此,当前阶段的贡献:中点值 \(\times\) 区间数量 + 总偏移量和,即 \(\frac{n + 1}{2} \times 2^{i - 1} + add[i]\),都能\(O(1)\)得到,而阶段数是\(O(\log n)\)的,因此一次询问的时间复杂度是\(O(\log n)\)的。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = unsigned long long;

int main(void) {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    auto solve = [&](int n, int k) {
        LL ans = 0;
        __int128 nn = 1;
        __int128 add = 0;
        while (n) {
            if (n < k)
                break;
            if (n & 1) {
                ans += (n + 1) / 2 * nn + add;
            }
            add = add * 2 + (n + 1) / 2 * nn;
            nn *= 2;
            n /= 2;
        }
        return ans;
    };
    int t;
    cin >> t;
    while (t--) {
        int n, k;
        cin >> n >> k;
        LL ans = solve(n, k);
        cout << ans << '\n';
    }

    return 0;
}


D - Refined Product Optimality (cf2053 D)

题目大意

给定两个数组 \(a,b\),长度均为 \(n\)。

可以打乱数组\(b\),最大化 \(\prod_{i=1}^{n} min(a_i, b_i)\)。

维护 \(q\) 次操作,每次操作,将让 一个数组的一个元素加一,然后回答其最大值。

结果对 \(998244353\) 取模。

解题思路

首先考虑如何最大化乘积。可以发现,两者都从小到大排序时,乘积最大。

可以这么考虑:我们从所有数的大到小考虑,对于每个数,比如是 \(a_i\),如果它对答案有贡献,那么要从 \(b\) 中找到一个数 \(b_j\),使得 \(b_j \ge a_i\),这样才能使得 \(min(a_i,b_j) = a_i\),如果没有这样的数,它对答案没有贡献,我们就把它放在 \(a\)的池子里。接下来考虑的数比如 \(b_i\),如果它对答案有贡献,那么要从 \(a\) 中找到一个数 \(a_j\),使得 \(a_j \ge b_i\),这样才能使得 \(min(a_j,b_i) = b_i\),而此时 \(a\) 的池子里的数一定都比 \(b_i\) 大,因此我们可以直接取出来与 \(b_i\) 匹配。

即维护两个数组,一个是 \(a\) 的池子,一个是 \(b\) 的池子,然后从大到小考虑,如果当前数对答案有贡献,就从另一个数组的池子里找一个数匹配,否则就放到自己的池子里。容易发现这样匹配的结果,就是两个数组从小到大排序的结果。

知道最优情况怎么求了,接下来考虑如何维护操作。

每次操作只会让一个数加一,看着变化不大,容易想到一个朴素的维护方法:加一后,与右边的数比较,如果比右边的数大,就交换,直到不再比右边的数大为止。交换的同时维护答案。但这样的时间复杂度有问题。

考虑数组\(a\):\(1,1,1,1,1,1,2\),第一次操作将第一个数加一,则变成\(1,1,1,1,1,2,2\),经历了\(O(n)\)次交换,然后再让第一个数加一,变成\(1,1,1,1,2,2,2\),又经历了\(O(n)\)次交换,这样的时间复杂度是\(O(qn)\),会超时。

怎么办呢?考虑上述朴素做法的问题:当我们第一个数加一时,不断交换位置时,其实答案是不变的:因为交换的两个数是同一个值。而从操作前和操作后的结果对比,其实它等价于让当前数的最右边的数加一,就没了

因此,对于当前操作,假设对数组\(a\)操作,以及一个排了序的数组\(sorta\),当前对\(a_i = p\)加一,它等价于在\(sorta\)中找到最右边的\(sorta_i = p\),然后让\(sorta_{i}\)加一,同时维护答案(即先除以原来的数,操作后再乘以新的数)。

如何找到最右边的\(sorta_i = p\)呢?因为\(sorta\)是排好序的,可以用二分查找,upper_bound的前一个就是最右边的\(p\)。

这样的时间复杂度是\(O(q\log n)\)的。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;

const int mo = 998244353;

int qpower(int a, int b) {
    int qwq = 1;
    while (b) {
        if (b & 1)
            qwq = 1ll * qwq * a % mo;
        a = 1ll * a * a % mo;
        b >>= 1;
    }
    return qwq;
}

int inv(long long x) { return qpower(x, mo - 2); }

int main(void) {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int t;
    cin >> t;
    while (t--) {
        int n, q;
        cin >> n >> q;
        array<vector<int>, 2> a{vector<int>(n), vector<int>(n)};
        for (auto& x : a)
            for (auto& y : x)
                cin >> y;
        auto sorta = a;
        for (auto& x : sorta)
            sort(x.begin(), x.end());
        LL ans = 1;
        auto get = [&](int x) { return min(sorta[0][x], sorta[1][x]); };
        for (int i = 0; i < n; ++i) {
            ans = ans * get(i) % mo;
        }
        cout << ans << ' ';
        while (q--) {
            int o, x;
            cin >> o >> x;
            --o;
            --x;
            int val = a[o][x];
            int pos = prev(upper_bound(sorta[o].begin(), sorta[o].end(), val)) -
                      sorta[o].begin();
            ans = ans * inv(get(pos)) % mo;
            a[o][x] = val + 1;
            sorta[o][pos] = val + 1;
            ans = ans * get(pos) % mo;
            cout << ans << " \n"[q == 0];
        }
    }

    return 0;
}



E - Resourceful Caterpillar Sequence (cf2053 E)

题目大意

给定一棵树,对于一对点\((p, q)\),定义一个毛毛虫序列:\(p \to q\)的路径上的点,其中\(p\)是毛毛虫的头,\(q\)是毛毛虫的尾。

两人博弈,先手拉着毛毛虫的头,后手拉着毛毛虫的尾,两人轮流执行操作。每次可以选择一个点,然后将毛毛虫的头或尾连同身子往前移动到这个点的子节点上。

如果头到了叶子节点,先手胜利,如果尾到了叶子节点,后手胜利,如果始终无法到达叶子节点,平局。

两人都会采取最优策略,问\((p,q)\)的数量,使得后手必胜。

解题思路

考虑后手必胜的情况,容易想到的一个是:

  • \(q\)在叶子节点,\(p\)不在叶子节点,那么后手必胜。这个统计一下叶子数量即可得到。

还有一种情况是,先手移动后,\(q\)在一个特别的位置,它可以一步移动到叶子节点,这样后手必胜。

我们定义能一步到叶子节点的点为好点,对于\(q\)点,考虑其每一个邻居\(u\),如果\(u\)是好点,那么\((u,q)\)这对点对答案有贡献,考虑贡献怎么求。

由于先手移动一步后,\(q \to u\),那么\(p\)得在\(u\)的子节点上,这样先手移动才有\(q \to u\),考虑\(p\)的取值:

  • 不能在叶子上
  • 不能在好点
  • 其他点都可以

因此就是统计一个子树里非叶子非好点的点的数量。一个简单的计数问题,可以用DFS解决。

选定一个点为根后,对于每个点\(q\),考虑其儿子\((u,q)\),还有父亲\((fa, q)\),计算它们的贡献即可。

时间复杂度是\(O(n)\)。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;

int main(void) {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int t;
    cin >> t;
    while (t--) {
        int n;
        cin >> n;
        vector<vector<int>> edge(n);
        vector<int> du(n);
        for (int i = 0; i < n - 1; ++i) {
            int u, v;
            cin >> u >> v;
            --u, --v;
            edge[u].push_back(v);
            edge[v].push_back(u);
            du[u]++;
            du[v]++;
        }
        if (n == 2) {
            cout << 0 << '\n';
            continue;
        }
        int leaves = 0;
        vector<int> good(n);
        for (int i = 0; i < n; ++i) {
            if (du[i] == 1) {
                leaves++;
                for (int v : edge[i]) {
                    good[v] = 1 && (du[v] != 1);
                }
            }
        }
        int goods = accumulate(good.begin(), good.end(), 0);
        LL ans = 1ll * leaves * (n - leaves);
        auto dfs = [&](auto& dfs, int u, int fa) -> array<int, 3> {
            int sum = good[u];
            int sz = 1;
            int leave = du[u] == 1;
            for (int v : edge[u]) {
                if (v == fa)
                    continue;
                auto [nsum, nleave, nsz] = dfs(dfs, v, u);
                if (good[v]) {
                    ans += nsz - nleave - nsum;
                }
                sum += nsum;
                sz += nsz;
                leave += nleave;
            }
            if (u != fa && du[u] != 1) {
                if (good[fa]) {
                    int ano_sum = goods - sum;
                    int ano_sz = n - sz;
                    int ano_leave = leaves - leave;
                    ans += ano_sz - ano_leave - ano_sum;
                }
            }
            return {sum, leave, sz};
        };
        int root = 0;
        while (root < n && du[root] == 1)
            root++;
        dfs(dfs, root, root);
        cout << ans << '\n';
    }

    return 0;
}


标签:Good,frac,int,auto,cin,偏移量,2024,区间,Bye
From: https://www.cnblogs.com/Lanly/p/18638613

相关文章

  • [luoguP10218/省选联考 2024] 魔法手杖
    题意给定\(a_1,a_2,\dots,a_n\)以及\(b_1,b_2,\dots,b_n\),满足\(a_i\in[0,2^k-1]\)以及\(b_i\geq0\),你需要给出\(S\subseteq\{1,2,\dots,n\}\)以及\(x\in[0,2^k-1]\)满足以下条件:\(\sum\limits_{i\inS}b_i\leqm\);满足以上条件的前提下,最大化\(val(S,x)......
  • 2024-2025-1 学号20241315《计算机基础与程序设计》第十四周学习总结
    作业信息这个作业属于哪个课程2024-2025-1-计算机基础与程序设计)这个作业要求在哪里<作业要求的链接>https://www.cnblogs.com/rocedu/p/9577842.html#WEEK14这个作业的目标<写上具体方面>《C语言程序设计》第13-14章并完成云班课测试作业正文https://www.cnbl......
  • 2024-2025-1 20241415《计算机基础与程序设计》第十四周学习总结
    2024-2025-120241415《计算机基础与程序设计》第十四周学习总结作业信息这个作业属于哪个课程2024-2025-1-计算机基础与程序设计这个作业要求在哪里2024-2025-1计算机基础与程序设计第十四周作业这个作业的目标自学《C语言程序设计》第13-14章作业正文https:......
  • 2024第一届Solar杯应急响应挑战赛WP
    日志流量日志流量-1直接放到D盾分析解码flag{A7b4_X9zK_2v8N_wL5q4}日志流量-2哥斯拉流量工具解一下flag{sA4hP_89dFh_x09tY_lL4SI4}日志流量-3tcp流6复制data流解码改pdfflag{dD7g_jk90_jnVm_aPkcs}内存取证内存取证-1vol.py-f123.raw--profile=Win7SP......
  • 2024年种子轮融资趋势:科技引领,消费降温
    引言2024年的种子轮投资市场呈现出显著的技术驱动特征,尤其是在人工智能(AI)、软件即服务(SaaS)、网络安全、医疗科技以及深科技领域,投资者表现出了浓厚的兴趣。与此同时,传统消费品和直接面向消费者(DTC)零售等领域则遭遇了融资瓶颈。本文将深入分析这些变化背后的原因,并探讨未来可......
  • 2024-2025-1 20241308《计算机基础与程序设计》第十四周学习总结
    作业信息这个作业属于哪个课程 2024-2025-1计算机基础与程序设计这个作业要求在哪里 2024-2025-1计算机基础与程序设计第十四周作业这个作业的目标 学习二进制文件和文本文件,文件的打开和关闭,顺序读写与随机读写,标准输入和输出及其重定向作业正文教材学习内容总结1.学习二进......
  • 2024北京知码狐信息集训总结
    你这集训,真令我欢喜!为期两周的集训(天堂生活)也是结束了,地狱(文化课)在召唤!集训的收获关于这次集训,我的收获自认为超过待在监狱(学校)半年,主要分为这几个方面:lxl实在是太强啦!他的课带来的收获占了整个集训的\(\frac{1}{3}\),尤其是他讲的插入-标记-删除算法,简洁易懂,见题就秒,蓝紫......
  • 高级java每日一道面试题-2024年12月27日-并发篇-锁的优化机制了解吗 ?
    如果有遗漏,评论区告诉我进行补充面试官:锁的优化机制了解吗?我回答:在Java高级面试中,锁的优化机制是一个重要且常见的考点。以下是对Java锁优化机制的详细解释:一、锁的基本概念锁是多线程编程中至关重要的同步机制,用于确保线程间共享数据的正确性。然而,使用锁也会引......
  • NOIP2024 游记
    前情提要:CSP2024游记luogucnblog省流:95+100+40+8=243,打的跟小丑一样。11.13(Day-17)作业好多。11.14(Day-16)水了点题。11.15(Day-15)晚上打了CF987Div2,多测注意T的范围!!!多测注意T的范围!!!多测注意T的范围!!!多测注意T的范围!!!多......
  • 2024第一届Solar杯应急响应挑战赛
    2024第一届Solar杯应急响应挑战赛附件密码:KzXGabLkDjs&j@3a&fAayNmD数据库这里导入镜像有个问题会报错FailedtowritecontenttodiskF:\长城杯+国赛\应急比赛\【题目】小题+综合题\solar\mssql\mssql\\mssql-disk1.vmdk.Reason:Thereisnotenoughspaceonthefil......