首页 > 其他分享 >NOIP2023模拟5联测26 题解

NOIP2023模拟5联测26 题解

时间:2023-10-28 12:13:31浏览次数:40  
标签:dots 26 val int 题解 sum edge NOIP2023

NOIP2023模拟5联测26 题解

感觉我这场的官方题解写的是真的挺好的,所以我只能作少量补充。你可以直接去看官方题解,如果你想的话。

T1 x

题解

\(n = 2\) 没啥可说的。\(\color{white}{这档分你要是没拿到那你还是蛮强的。}\)

\(n = 3\) 的时候,我们需要比较 \((a_1^{a_2})^{a_3}\) 与 \(a_1^{(a_2^{a_3})}\) 的大小。

稍微变一变形,就是比较 \(a_1^{a_2 \cdot a_3}\) 与 \(a_1^{(a_2^{a_3})}\) 的大小。

这就很好比了,直接比 \(a_2 \cdot a_3\) 与 \(a_2^{a_3}\) 的大小就好了。

所以当 \(a_2 > 1\) 时,直接用 \(a_2 \cdot a_3\) 当做 \(a_1\) 的指数就好了。

但当 \(a_2 = 1\) 时,我们让 \(a_2^{a_3}\),即 \(1\),作为 \(a_1\) 的指数就好了。

那么对于所有的情况,我们先设一个 \(tot\),为 \(a_1\) 最终的指数。

然后我们从 \(2\) 到 \(n\) 枚举 \(i\),如果 \(a_i \not= 1\),直接让 \(tot\) 乘上 \(a_i\),如果 \(a_i = 1\),直接停就好了。

然后快速幂就好了。

注意一下费马小定理的应用就好了。

就好了。

代码

点击查看代码
#include <bits/stdc++.h>
#define int long long
#define mod 998244353

using namespace std;

int n, a, ans = 1, tot = 1, x;

inline int quick_pow(int base, int ci) {
    int res = 1;
    while(ci) {
        if(ci & 1)
            res = res * base % mod;
        base = base * base % mod;
        ci >>= 1;
    }
    return res;
}

signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);
    cin >> n >> a;
    if(a == 1) {
        putchar('1');
        return 0;
    }
    for(int i = 2; i <= n; ++ i) {
        cin >> x;
        if(x == 1)
            break;
        tot *= x;
        tot %= (mod - 1);
    }
    cout << quick_pow(a, tot) << '\n';
}

T2 u

题解

先设个东西,下面要用:\(\displaystyle f(x) = \frac{x (x - 1)}{2}\)

假设对于一个权值 \(val\),我们去考虑所有权值小于 \(val\) 的边。

先来考虑给定的、权值小于 \(val\) 的边。假设这些边将图连成了 \(k\) 个连通块,块的大小分别为 \(s_1, s_2, \dots, s_k\)。

再来考虑没有给定的、权值小于 \(val\) 的边。我们要让加边前后最小生成树的权值不变,那么我们需要让这些边所连接的两个端点在同一块内,否则最小生成树的边集一定会包含那个边。如果理解不了的话,你可以去考虑 Kruskal 的过程:我们要按边权从小到大枚举每一条边,如果当前边的两个端点在同一块内就不进行操作,否则将它们所在的块合并。那么对于这些没有给定的边,我们要让这些边的左右端点在同一块内,才不会对最终的最小生成树造成影响。

所以我们现在需要考虑,能否让所有权值小于 \(val\) 的、没有给定的边的两个端点在同一块内。不好直接判断,那么我们就去算一下当前局势还能容纳多少没有给定的边。

对于这 \(k\) 个块,我们不能合并任意两个块,那么一共最多会有 \(sum = f(s_1) + f(s_2) + \dots + f(s_k)\) 条边。所以当且仅当没有给定的、权值小于 \(val\) 的边数小于等于 $sum - $ 块内相连的给定的边时,当前局势合法,也就是最终答案可能为 \(Yes\)。

如果对于每一个 \(val\),当前局势都是合法的,那么最终答案才为 \(Yes\),只要有任一局势不合法,最终答案就为 \(No\)。

考虑怎么实现。

维护块,并查集会吧?

维护块的大小,并查集的 \(size\) 会吧?

维护块内相连的边的数量,将一个点的出边转给另一个点会吧?

然后我们再来考虑其他的。

不难发现,我们不需要对于每一个权值都判断一下是否合法(\(M \le 19999900000\),每个权值都判断会 T 飞),因为保证给出的 \(m\) 条边能使所有点连通,并且如果没有给定边权为 \(val - 1\) 的边且 \(val\) 的局势合法,那么 \(val - 1\) 的局势一定也合法,所以我们只需要枚举给出的 \(m\) 个边权即可。

然后考虑怎么维护 \(sum\)。假设这次操作将大小为 \(s_1\)、\(s_2\) 的两个块合并,那么新的 \(sum\) 等于原来的 \(sum + s_1 \times s_2\)。

嗯,没有了,自己打去吧。

感觉我说的不是人话。

代码

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;

const int N = 500005;
struct E {
    int w;
    int u;
    int v;
}edge[N];

int n, m, sum, fa[N], siz[N], tot;
vector<int> e[N];

inline bool cmp(E a, E b) {return a.w < b.w;}

inline int findf(int x) {
    while(x != fa[x])
        x = fa[x] = fa[fa[x]];
    return x;
}

signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);
    cin >> n >> m;
    for(int i = 1; i <= n; ++ i)
        fa[i] = i, siz[i] = 1;
    for(int i = 1; i <= m; ++ i) {
        cin >> edge[i].u >> edge[i].v >> edge[i].w;
        e[edge[i].u].push_back(edge[i].v);
        e[edge[i].v].push_back(edge[i].u);
    }
    stable_sort(edge + 1, edge + 1 + m, cmp);
    for(int i = 1; i <= m; ++ i) {
        if(edge[i].w - i > sum - tot) {
            cout << "No\n";
            return 0;
        }
        int fu = findf(edge[i].u), fv = findf(edge[i].v);
        if(fu != fv) {
            if(siz[fu] > siz[fv])
                swap(fu, fv);
            sum += siz[fu] * siz[fv];
            for(int j = 0; j < e[fu].size(); ++ j) {
                if(findf(e[fu][j]) != fv)
                    e[fv].push_back(e[fu][j]);
                else
                    ++ tot;
            }
            siz[fv] += siz[fu];
            fa[fu] = fv;
        }
    }
    cout << "Yes\n";
}

T3 a

题解

Subtask 1:

暴力枚举。

Subtask 2:

状压 dp。题解是这么说的,但我不知道压啥。

Subtask 3:

单降了都,那就是个栈。卡特兰数。单增输出 \(1\) 就行。

Subtask 4:

一个 \(B\) 序列合法,当且仅当 \(1\) 和 \(2\) 的数量与 \(A\) 序列相同,并且对于每一个 \(i\),\(B\) 的长度为 \(i\) 的前缀中 \(1\) 的数量都不少于 \(A\) 的长度为 \(i\) 的前缀中 \(1\) 的数量。

所以直接 dp 即可。

Subtask 5:

已经很接近正解了。

我们设 \(n\) 在 \(A\) 序列和 \(B\) 序列中的位置分别为 \(p, q\),即 \(A_p = B_q = n\)。

考虑到,这是一个小根堆,而且这是一个排列,所以我们拿出 \(n\) 就意味着堆此时空了。所以 \(B\) 的前 \(q\) 个元素就是 \(A\) 的前 \(q\) 个元素,只不过顺序可能不太一样。所以 \(\{A_1, A_2, \dots, A_q\} = \{B_1, B_2, \dots, B_q\}\),且 \(\{A_{q + 1}, A_{q + 2}, \dots, A_n\} = \{B_{q + 1}, B_{q + 2}, \dots, B_n\}\)。

然后我们已经知道 \(B_q = n\),所以我们可以把原问题看做两个子问题:\([1, q - 1]\) 和 \([q + 1, n]\)。其中,\(B_1, B_2, \dots, B_{q - 1}\) 是由 \(A_1, A_2, \dots, A_q\) 去掉 \(A_p\) 经过一些操作变化而来的,而 \(B_{q + 1}, B_{q + 2}, \dots, B_n\) 是由 \(A_{q + 1}, A_{q + 2}, \dots, A_n\) 经过一些操作变化而来的。

所以我们考虑递归地解决子问题。

找当前序列的最大值,然后枚举其在 \(B\) 中的位置,然后分成两个子序列,分治。注意左边要把最大值删去。不难看出来,子序列 \(A'\) 一定是原序列 \(A\) 的某一区间 \([l, r]\) 删掉一些较大数后得到的。

整合一下现在的信息,我们发现持续在变化的当前只有处理的子序列 \(A'\) 所对应的 \(l, r\),以及删掉了哪些数。所以我们设 \(f_{l, r, i}\) 表示 \(A_l, \dots, A_r\) 中,不超过 \(i\) 的数构成的子序列的答案,或者说是情况数。

考虑怎么转移。

  • 当 \(i\) 不在 \([l, r]\) 时,\(f_{l, r, i} = f_{l, r, i - 1}\),这很显然。

  • 当 \(i\) 在 \([l, r]\) 时,我们假设 \(A_m = i\),那么我们再去枚举 \(i\) 在 \(B\) 中的位置 \(x\),就有:

\[\begin{aligned} f_{l, r, i} = \sum_{x = m}^r f_{l, x, i - 1} \cdot f_{x + 1, r, i - 1} \end{aligned}\]

如果你看了官方题解那个式子,你会发现不太一样,但是结果是一样的,因为这是一个排列,所以在 \([x + 1, r]\) 中不会再出现一个 \(i\),所以 \(f_{x + 1, r, i - 1} = f_{x + 1, r, i}\),至于为什么我这么写,因为我觉得这样可能更好理解。

直接 dp 就行了,时间复杂度 \(O(n^4)\),能过。

Subtask 1 \(\sim\) 5:

我们考虑怎么处理 \(A\) 是一个数列而不是排列的情况。

假设一个数 \(i\) 出现次数大于 \(1\),那么我们将其第 \(j\) 次出现设为 \(i_j\)。

首先,我们约定,如果堆中最小值为 \(i\),并且堆中存在多个 \(i\),那么我们在取的时候先取下标最小的,也就是值相等的情况下满足先进先出的原则。

换句话说,\(a_b < c_d\) 当且仅当 \(a < c\),或者 \(a = c\) 且 \(b < d\)。

那么在我们约定的条件下,可以把原数列转化为一个排列。

考虑这样转化的正确性。

转化后 \(B\) 序列中的每一个数都能唯一对应未转化的 \(B\) 序列中的一个数,而且如果 \(j < j'\),那么一定有 \(i_j\) 在 \(B\) 中排在 \(i_{j'}\) 之前,这样就得到了一个自然双射,也就完成了证明。

代码

点击查看代码
#include<bits/stdc++.h>
using namespace std;

const int N = 105;
const int mod = 1000000007;
int n, a[N], b[N], cnt[N], rk[N], f[N][N][N];

signed main() {
	cin >> n;
	for(int i = 1; i <= n; ++ i)
        scanf("%d",a + i), ++ cnt[a[i]];
	for(int i = 1; i <= n; ++ i)
        cnt[i] += cnt[i - 1];
	for(int i = n; i; -- i)
        b[i] = cnt[a[i]] --, rk[b[i]] = i;
	for(int i = 1; i <= n + 1; ++ i)
		fill(f[i][i - 1], f[i][i - 1] + n + 1, 1);
	for(int l = n; l; -- l) {
        for(int r = l; r <= n; ++ r) {
            f[l][r][0] = 1;
            memcpy(f[l][r] + 1, f[l][r - 1] + 1, 4 * b[r] - 4);
            memcpy(f[l][r] + 1, f[l + 1][r] + 1, 4 * b[l] - 4);
            for(int i = max(b[l], b[r]); i <= n; ++ i)
                if(rk[i] < l || rk[i] > r)
                    f[l][r][i] = f[l][r][i-1];
                else
                    for(int j = rk[i]; j <=r; ++ j)
                        if(b[j] <= i)
                            f[l][r][i] = (f[l][r][i] + 1ll * f[l][j][i - 1] * f[j + 1][r][i]) % mod;
        }
    }
	cout << f[1][n][n];
}

T4 y

求教教,我只能看懂题解的一小部分。不太擅长串串和图论。

标签:dots,26,val,int,题解,sum,edge,NOIP2023
From: https://www.cnblogs.com/Meteor-streaking/p/17793940.html

相关文章

  • P4260 博弈论与概率统计
    传送门description\(T\)次询问,每次给定\(n,m,p\),总共\(n+m\)局游戏,每局A有\(p\)的概率获胜。一局游戏获胜A的得分加1,否则减1,但是如果A在得分为0的情况下输了一局,得分不变。求A赢\(n\)局,输\(m\)局后游戏结束时A的得分的数学期望。\(n,m,T\leq2.5\time......
  • redshift DATE_TRUNC函数 查询日期上个月的26号到当前月的26号
    redshiftDATE_TRUNC函数查询日期上个月的26号到当前月的26号#redshift脚本#2023-08-0100:00:00.000selectDATE_TRUNC('month',current_date-INTERVAL'2month')#2023-08selectleft(DATE_TRUNC('month',current_date-INTERVAL'2month......
  • [TopCoder 13001] BigO 题解
    [TopCoder13001]BigO题解题目描述给定一张有向图,当\(L\)趋近于无穷大时,长度为\(L\)的路径条数有\(S\)条,此时若\(S=O(L^k)\),输出\(k\),否则如果没有多项式的大O表示法,输出\(-1\)。指数情况首先如果一张图中存在如下强连通分量,则\(S=O(2^L)\)。因为每次到1......
  • 10.26
    上午上了统一建模语言,讲了状态机,然后上了蓝球体育课,讲了全场三步上篮,以及运球进攻,最后进行了比赛,比赛输了,但是收获了很多,下午上了数据结构和离散数学,数据结构讲了图论,离散数学,讲了极大元,极小元,下界,上界。......
  • P7650 题解
    非常好题目,第一步都想不出来。可以观察出来最优方案必定是从大往小将\(x\)放到\(x+1\)前,有可能不动,中间的比他小的一定要放到前面去。考虑用dp计算最小值。这里是这道题最重要的一步:相对位置的变化非常不好描述,考虑将所有数固定。一次操作改为:不影响其他其他数的位置,将一......
  • 题解 P4285 [SHOI2008] 汉诺塔
    具体思路设\(f_{i,x}\)表示\(i\)个盘子从\(x\)柱子出发的步数。设\(g_{i,x}\)表示\(i\)个盘子从\(x\)柱子出发到哪个柱子。记\(y=g_{i-1,x}\),\(z=6-x-y\)。其中,\(y\)代表将前\(i-1\)个盘子从\(x\)柱子移到的柱子,\(z\)代表剩下的那个柱子。分类讨论。若......
  • P2230 Tinux系统 题解
    传送门提供一种基于贪心的解法。首先是将\(p\)从小到大排序考虑到该系统是一棵树,所以对于系统中的每个点,我们记:\(tr_{son}\)表示该点(目录)的儿子的位置\(tr_{fa}\)表示该点(目录)的父亲的位置\(tr_{siz}\)表示该点(目录)包含的点的个数\(tr_{cnt}\)表示该点(目录)有......
  • YACS 2023年10月月赛 甲组 题解
    目前只有T2,其他题目我在看。题目链接1题目链接2题目链接3T2很简单的一道题,将图分为若干个连通块,然后分别求最小生成树。从货车运输中得到的结论,最小生成树等价于最小边权上限生成树,也就是它也能够保证选出边中最大的边权最小。而题目中明确说了这个最小生成树的权值是其中......
  • 26. 删除有序数组中的重复项
    1.题目介绍给你一个非严格递增排列的数组nums,请你原地删除重复出现的元素,使每个元素只出现一次,返回删除后数组的新长度。元素的相对顺序应该保持一致。然后返回nums中唯一元素的个数。考虑nums的唯一元素的数量为k,你需要做以下事情确保你的题解可以被通过:更......
  • [NOI2010] 超级钢琴 题解
    [NOI2010]超级钢琴题解说点闲话原本不想写这个题解的但是看到我的代码居然长度为2048B->刚好2KiB,然后还跟题号相同QAQ题目翻译给你一段序列,求出其中从第\(1\)大到第\(k\)大的子区间的和。思路解析首先可以想到一个简单的暴力,对于每一个区间开头\(i\),和区间结尾\(j\),都求......