比赛链接

C

题解

方法一

知识点:思维。

先统计没有出现的数,每个都可以随便放,所以作为补位用的。

将原数组左移一位作为预定的答案数组,然后开始检查。如果和原数组一样,则用补位数字填充,如果不一样就不动。

这样做是肯定能构造出一个合法的数组的,因为左移以后这个数字无论重复不重复一定能有一个是和对应位置不一样的,于是这个数字就被用掉了。因此,原数组出现的所有数字都能有一个到位,剩余位置都是和答案数组重复,就一定能给到补位的数字。

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

空间复杂度 \(O(n)\)

方法二

知识点:思维。

先构造一个 \([1,...,n]\) 的预定的答案数组,然后开始检查对应位置是否重复。如果重复就用后面一个数字和这个数字交换,一定能保证这个位置不重复。这样可以构造到 \(n-1\) 位置,最后一个位置要特殊处理,因为如果重复没有后面换的数字了,需要从前面找一个交换。

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

空间复杂度 \(O(n)\)

代码

方法一

#include <bits/stdc++.h>
#define ll long long using namespace std; int a[100007];
bool vis[100007]; bool solve() {
int n;
cin >> n;
for (int i = 1;i <= n;i++) vis[i] = 0;
for (int i = 1;i <= n;i++) {
cin >> a[i];
vis[a[i]] = 1;
}
vector<int> v;
int cnt = 0;
for (int i = 1;i <= n;i++) {
if (!vis[i]) v.push_back(i);
else vis[i] = 0, cnt++;
}
if (cnt == 1) return false;
cout << "YES" << '\n';
for (int i = 2;i <= n;i++) {
if (vis[a[i]] || a[i] == a[i - 1]) {
cout << v.back() << ' ';
v.pop_back();
}
else {
cout << a[i] << ' ';
vis[a[i]] = 1;
}
}
if (v.empty()) cout << a[1] << '\n';
else cout << v.back() << '\n';
return true;
} int main() {
std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int t = 1;
cin >> t;
while (t--) {
if (!solve()) cout << "NO" << '\n';
}
return 0;
}

方法二

#include <bits/stdc++.h>
#define ll long long using namespace std; int a[100007], b[100007]; bool solve() {
int n;
cin >> n;
for (int i = 1;i <= n;i++) {
cin >> a[i];
b[i] = i;
}
bool ok = true;
for (int i = 2;i <= n;i++)
ok &= a[i] == a[i - 1];
if (ok) return false;
for (int i = 1;i <= n - 1;i++)
if (a[i] == b[i]) swap(b[i], b[i + 1]);
if (a[n] == b[n]) {
for (int i = 1;i <= n - 1;i++) {
if (a[i] != b[n]) {
swap(b[i], b[n]);
break;
}
}
}
cout << "YES" << '\n';
for (int i = 1;i <= n;i++) cout << b[i] << ' ';
cout << '\n';
return true;
} int main() {
std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int t = 1;
cin >> t;
while (t--) {
if (!solve()) cout << "NO" << '\n';
}
return 0;
}

F

题解

方法一

知识点:模拟,贪心。

用一个数组存数字,另一个数组用下标指向模拟链表,每次删除完成修改下标指向,当作链。再用个数组记录是否被删掉。

由于是从左往右遍历,向左扩展就用链表数组,向右扩展就只需要加一。如果右端点碰到了之前删掉过的数字就可以直接结束了,因为这个数字只可能通过之前向左扩展到这里,也就是说之后的所有数字都被删掉了,不需要继续了。

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

空间复杂度 \(O(n)\)

方法二

知识点:贪心。

一个最简单的写法,直接在线处理输入的点。用双端队列存储数字,进入一个数就和队尾匹配看能不能删除,不能删除就存入;如果队空也直接存入。

最后输入完了,我们只处理了能向左删除的数字,没有处理向右的尾部到头部的环状性质删除,因此还需要继续处理。

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

空间复杂度 \(O(n)\)

代码

方法一

#include <bits/stdc++.h>

using namespace std;

int a[100007], p[100007];
bool vis[100007]; int main() {
std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int n, x;
cin >> n >> x;
int cnt = 0;
for (int i = 0;i < n;i++) cin >> a[i];
for (int i = 0;i < n;i++) p[i] = (i - 1 + n) % n;
for (int t = 0;t < n;t++) {
if (vis[t]) continue;
int i = t, j = (t + 1) % n;
if (vis[j]) break;
while (i != j && (a[i] == a[j] || a[i] + a[j] == x)) {
vis[i] = 1;
vis[j] = 1;
cnt++;
i = p[i];
j = (j + 1) % n;
if (vis[j]) break;
}
p[j] = i;
}
cout << cnt << '\n';
return 0;
}

方法二

#include <bits/stdc++.h>

using namespace std;

int main() {
std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int n, x;
cin >> n >> x;
deque<int> dq;
int cnt = 0;
for (int i = 0, tmp;i < n;i++) {
cin >> tmp;
if (!dq.empty() && (dq.back() == tmp || dq.back() + tmp == x)) dq.pop_back(), cnt++;
else dq.push_back(tmp);
}
while (dq.size() >= 2 && (dq.front() == dq.back() || dq.front() + dq.back() == x)) {
dq.pop_front();
dq.pop_back();
cnt++;
}
cout << cnt << '\n';
return 0;
}

G

题解

知识点:字符串,模拟。

分类讨论一下。

\(n=1\) ,最短长度 \(2\) , a. ,\(2\) 种。

\(n=2\) ,最短长度 \(2\) ,aba..b...*.+ ;如果两个字母一样还可以,a+a* ,\(6\) 或 \(8\) 种。

\(n\geq3\) ,最短长度 \(2\) ,.*.+ ;如果字母一样还可以,a+a* ,\(2\) 或 \(4\) 种。

也就是说,( ) | ? 都没用,\(998244353\) 也没用。诈骗题qwq。

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

空间复杂度 \(O(n)\)

代码

#include <bits/stdc++.h>

using namespace std;

const int mod = 998244353;

int main() {
std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int q;
cin >> q;
while (q--) {
string s;
cin >> s;
int n = s.size();
if (n == 1) cout << 1 << ' ' << 2 << '\n';
else if (n == 2) {
cout << 2 << ' ';
if (s[0] == s[1]) cout << 8 << '\n';
else cout << 6 << '\n';
}
else if (n >= 3) {
bool ok = true;
for (auto c : s) ok &= s[0] == c;
cout << 2 << ' ';
if (ok) cout << 4 << '\n';
else cout << 2 << '\n';
}
}
return 0;
}

J

题解

知识点:计数dp,排列组合。

首先是一个套路的区间和问题转化为前缀和端点问题。由于数字范围是 \([0,k-1]\) ,在 \(\mod k\) 下的前缀和是唯一对应的,即一个前缀和序列能唯一确定一个原序列,所以我们可以通过直接考虑前缀和序列,替代考虑原序列。

题目要求区间和能被 \(k\) 整除的区间是有贡献的,也就是前缀和序列的两个不同端点 \(i,j\) 满足 \(sum[i] \equiv sum[j](\mod k)\) 就对应了一个区间满足。特别地,当 \(sum[i] = 0\) 时,即单点整除,自己就能成为一个区间要特判。

对此不妨先看一个简单的版本,给定了一个序列,求区间满足区间和能被 \(k\) 整除的个数,即贡献。根据上面说的,这个序列对应了唯一一个前缀和序列,而前缀和序列只需要两个不同端点满足模 \(k\) 余数相同或者一个点自己能被 \(k\) 整除即可。因此,找出 \([0,k-1]\) 每个数在前缀和序列出现的次数 \(cnt_i\),于是每个数的贡献是 \(\sum C_{cnt_i}^2\) ,再特判单点整除的情况 \(C_{cnt_0}^1 = cnt_0\) 。

在回来看原问题,求构造由 \([0,k-1]\) 组成的长度 \(n\) 的序列,且所有区间中满足区间和被 \(k\) 整除的个数(贡献)为 \(t\) 的方案数。我们可以在原问题的求解过程内增加一个dp前缀和序列的过程,转化成一个计数dp。

\(dp[i][j][u]\) 表示为考虑到第 \(i\) 个数,已经选了 \(j\) 个数进入前缀和序列, 贡献为 \(u\) 的方案数。

初始状态是 \(dp[0][0][0] = 1\)。

对于 \(dp[i-1][j][u]\) 的下一个状态,在 \(i > 1\) 时,即选的数大于 \(0\) ,不用考虑自身成区间的问题,因此选了 \(v\) 个第 \(i\) 个数后状态是 \(dp[i][j+v][u+C_v^2]\) ,因为新选的数与以前的数没关系只会自己和自己组合,贡献只需要加上 \(C_v^2\) 即可;而 \(i = 1\) ,即选 \(0\) 时,由于自己可以单独代表一个长度为 \(1\) 合法区间,因此下一个状态是 \(dp[i][j+v][u+C_{v+1}^2]\) ,其中 \(C_{v+1}^2 = C_{v}^{2} + C_{v}^{1}\) ,可以理解为在前缀和序列首元素之前有个 \(0\) 元素可以匹配。

接下来是状态转移方程,由于 \(dp[i-1][j][u]\) 已经代表了这个状态的所有方案,而新加进来的 \(v\) 个数只需要在 \(v+j\) 的位置里面选 \(v\) 个放入,剩下的数的方案刚好就是 \(dp[i-1][j][u]\) ,因此下一个状态的方案数是 \(C_{v+j}^{v}dp[i-1][j][u]\) 。因此转移方程是:

\[\left \{
\begin{aligned}
dp[i][j+v][u+C_{v+1}^{2}] &= C_{v+j}^{v}dp[i-1][j][u],i = 1\\
dp[i][j+v][u+C_{v}^{2}] &= C_{v+j}^{v}dp[i-1][j][u],i > 1
\end{aligned}
\right.
\]

最后这样直接写是会超时的,因为有很多分支是不必要的,发现 \(dp[i-1][j][0] = 0\) 时后面压根不需要选数就少了一个 \(v\) 循环。其实这种状态转移的方法叫刷表法,通过之前一个状态去更新它可能到达的所有状态;而填表法,通过现在的状态去找和它相关的状态去更新自己。这道题用填表法就没法优化,而用刷表法能立马看出优化的点。

注意数组的 \(u\) 维要开到两倍 \(t\) 的大小,也就是 \(n^2\) ,防止 \(u+v = t+c[n][2] \approx n^2\) 的情况出现。

组合数可以用 \(C_n^m = C_{n-1}^m + C_{n-1}^{m-1}\) 递推。

时间复杂度 \(O(n^2tk)\)

空间复杂度 \(O(ntk)\)

代码

#include <bits/stdc++.h>

using namespace std;

const int mod = 998244353;
int c[70][70];
int dp[70][70][70 * 70];///考虑到 i-1 个数字 ,已经选了j个,贡献k;70*70比2t大,不用担心u+c[v][2]越界 ///由于数字范围[0,k-1]因此原数组和前缀和数组是一一对应的,因此考虑枚举前缀和数组,将区间和转化为端点判断
///区间和能被k整除对应端点i,j有sum[i]%k = sum[j]%k(i!=j)
///特殊地,当sum[i] = 0时,自己也可以符合条件,因此要特判 int main() {
std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int n, k, t;
cin >> n >> k >> t;
for (int i = 0;i <= n + 1;i++) {
for (int j = 0;j <= i;j++) {
if (!j) c[i][j] = 1;
else c[i][j] = (c[i - 1][j] + c[i - 1][j - 1]) % mod;
}
}
///刷表法能剪枝,打表法不能
dp[0][0][0] = 1;
for (int i = 1;i <= k;i++) {
for (int j = 0;j <= n;j++) {
for (int u = 0;u <= t;u++) {
if (!dp[i - 1][j][u]) continue;
if (i == 1) {
for (int v = 0;v + j <= n;v++)
dp[i][j + v][u + c[v + 1][2]] = (dp[i][j + v][u + c[v + 1][2]] + 1LL * c[j + v][v] * dp[i - 1][j][u]) % mod;
///原本是c[v][2] 但 0可以自成一个,所以是c[v][2] + v = c[v+1][2] ,可以理解为在前缀和0号位有个0匹配
}
else {
for (int v = 0;v + j <= n;v++) {
dp[i][j + v][u + c[v][2]] = (dp[i][j + v][u + c[v][2]] + 1LL * c[j + v][v] * dp[i - 1][j][u]) % mod;
}
}
}
}
}
cout << dp[k][n][t] << '\n';
return 0;
}

蔚来杯2022牛客暑期多校训练营7 CFGJ的更多相关文章

  1. 蔚来杯2022牛客暑期多校训练营5 ABCDFGHK

    比赛链接 A 题解 知识点:图论,dp. 暴力建图,连接所有点的双向通路,除了原点是单向的,并且把路径长度作为权值. 随后,从原点出发(\(f[0] = 0\),其他点负无穷,保证从原点出发),按照权 ...

  2. 蔚来杯2022牛客暑期多校训练营6 ABGJM

    比赛链接 A 题解 知识点:数学,构造. 题目要求构造一个长为 \(m\) 的序列 \(c\) ,\(m\) 自选,使得 \(c\) 的无限循环序列 \(b\) 中任意连续 \(a_i\) 个数中都存 ...

  3. "蔚来杯"2022牛客暑期多校训练营9 G Magic Spells【马拉车+哈希】

    四川今天又上热搜了,继南部疫情的未雨绸缪后,龙槽沟是真的倾盆大雨了.我没有兴趣虚伪矫情地对罹难的游人表达同情,因为人与人互不相通徒增谈资:我也没有兴趣居高临下地对擅闯的愚人表达不屑,因为你我皆为乌合之 ...

  4. 2019牛客暑期多校训练营(第五场)G - subsequeue 1 (一题我真的不会的题)

    layout: post title: 2019牛客暑期多校训练营(第五场)G - subsequeue 1 (一题我真的不会的题) author: "luowentaoaa" c ...

  5. 2021牛客暑期多校训练营3 J 思维

    传送门 J-Counting Triangles_2021牛客暑期多校训练营3 (nowcoder.com) 题目 Goodeat finds an undirected complete graph ...

  6. B-xor_2019牛客暑期多校训练营(第四场)

    题意 给出n个数组(每组数个数不定),m个询问 l, r, x 序号在区间\([l,r]\)的每个数组是否都可以取出任意个数异或出x 题解 判断一个数组能否异或出x,是简单的线性基问题 判断多个线性基 ...

  7. 2019牛客暑期多校训练营(第九场)A:Power of Fibonacci(斐波拉契幂次和)

    题意:求Σfi^m%p. zoj上p是1e9+7,牛客是1e9:  对于这两个,分别有不同的做法. 前者利用公式,公式里面有sqrt(5),我们只需要二次剩余求即可.     后者mod=1e9,5才 ...

  8. 2019牛客暑期多校训练营(第一场)A题【单调栈】(补题)

    链接:https://ac.nowcoder.com/acm/contest/881/A来源:牛客网 题目描述 Two arrays u and v each with m distinct elem ...

  9. 2019牛客暑期多校训练营(第一场) B Integration (数学)

    链接:https://ac.nowcoder.com/acm/contest/881/B 来源:牛客网 Integration 时间限制:C/C++ 2秒,其他语言4秒 空间限制:C/C++ 5242 ...

随机推荐

  1. 推荐一款新框架PyScript:在 HTML 嵌入 Python 代码!

    一.介绍 网页浏览器是目前世界上最普遍,最可携的计算机环境.几乎所有人都可以在计算机或是手机上使用网页浏览器,以没有基础设施障碍的方式访问程序. 在 PyCon US 2022 上,知名 Python ...

  2. Dockerfile 使用 SSH

    如果在书写 Dockerfile 时,有些命令需要使用到 SSH 连接,比如从私有仓库下载文件等,那么我们应该怎么做呢? Dockerfile 使用 SSH Dockerfile 文件配置 为了使得 ...

  3. Html name与id属性的区别

    id----id属性规定为 HTML 元素的唯一的标识.当使用html,CSS时,id属性可用作链接锚(link anchor),通过 JavaScript(HTML DOM)getElementBy ...

  4. 二进制固件函数劫持术-DYNAMIC

    背景介绍  固件系统中的二进制文件依赖于特定的系统环境执行,针对固件的研究在没有足够的资金的支持下需要通过固件的模拟来执行二进制文件程序.依赖于特定硬件环境的固件无法完整模拟,需要hook掉其中依赖于 ...

  5. 《HALCON数字图像处理》第三章笔记

    目录 第三章 HALCON图像处理基础 HALCON控制语句 HALCON算子 HALCON图像处理入门 HALCON图像读取 HALCON图像显示 图形窗口 图像显示 显示文字 HALCON图像转换 ...

  6. JAVA - 缓冲和缓存

    JAVA - 缓冲和缓存 缓冲 Buffer 功能:协调上下层应用之间的性能差异.通过缓冲区的缓冲,当上层组件性能优于下层组件的时候,缓冲可以有效减少上层组件对下层组件的等待时间. 使用场景:IO流中 ...

  7. SpringBoot整合SpringSecurityOauth2实现鉴权-动态权限

    写在前面 思考:为什么需要鉴权呢? 系统开发好上线后,API接口会暴露在互联网上会存在一定的安全风险,例如:爬虫.恶意访问等.因此,我们需要对非开放API接口进行用户鉴权,鉴权通过之后再允许调用. 准 ...

  8. CMU15445 之 Project#0 - C++ Primer 详解

    前言 这个实验主要用来测试大家对现代 C++ 的掌握程度,实验要求如下: 简单翻译一下上述要求,就是我们需要实现定义在 src/include/primer/p0_starter.h 中的三个类 Ma ...

  9. linux函数与数组

    1. 函数的定义 方法1: function_name () { statement } 方法2: function function_name () { statement } --先定义后使用 例 ...

  10. NC202498 货物种类

    NC202498 货物种类 题目 题目描述 某电商平台有 \(n\) 个仓库,编号从 \(1\) 到 \(n\) . 当购进某种货物的时候,商家会把货物分散的放在编号相邻的几个仓库中. 我们暂时不考虑 ...