比赛链接

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. 『忘了再学』Shell基础 — 19、使用declare命令声明变量类型

    目录 1.declare命令介绍 2.声明数组变量类型 3.声明变量为环境变量 4.声明只读属性 5.补充: 1.declare命令介绍 Shell中所有变量的默认类型是字符串类型,如果你需要进行特殊 ...

  2. Beyond Compare文件对比神器,快来给文件找茬!

    在工作中很多场景下都需要比对两个文件之间的差异,你是否还傻傻的同时打开两个文件,用眼睛一行一行的核对? 赶紧来试试这个神器Beyond Compare!!它可以快速的帮你找出两个文件的差异,而且还有其 ...

  3. Node.js amqplib 连接 Rabbitmq 学习笔记

    var amqp = require('amqplib'); connect([url, [socketOptions]]) var amqp = require('amqplib/callback_ ...

  4. 【Redis】集群数据迁移

    Redis通过对KEY计算hash,将KEY映射到slot,集群中每个节点负责一部分slot的方式管理数据,slot最大个数为16384. 在集群节点对应的结构体变量clusterNode中可以看到s ...

  5. SAP 维护视图隐藏字段

    PBO:     MODULE reset_index. 其中ZDT_BPC002_T02 为视图名称. MODULE reset_index OUTPUT. FIELD-SYMBOLS:<fs ...

  6. ASP.NET MVC的核心-Controller(控制器)

    "每一个请求都必须通过Controller处理,然而其中有些请求是不需要模型和视图的" MVC框架规定带Controller后缀的类称为所谓的"控制器",在xx ...

  7. centos7 离线升级/在线升级操作系统内核

    目录 一.前言 二.系统环境 三.系统内核下载网址 四.centos7离线升级系统内核 1.先查看系统环境 2.离线升级系统内核 五.在线升级系统内核 一.前言 CentOS(Community EN ...

  8. 论文阅读 GloDyNE Global Topology Preserving Dynamic Network Embedding

    11 GloDyNE Global Topology Preserving Dynamic Network Embedding link:http://arxiv.org/abs/2008.01935 ...

  9. C# / VB.NET 将Html转为Word

    本文分享以C#程序代码为例,实现将Html文件转换Word文档的方法(附VB.NET代码).在实际转换场景中可参考本文的方法,转换前,请按照如下方法引用Word API的dll文件到Visual St ...

  10. 【cartogarpher_ros】二: 官方Demo的介绍与演示

    上一节我们介绍了在linux中快速安装集成ros环境的cartographer. 本节我们会来跑一些官方demo,用于测试cartographer是否正确安装,顺便看看cartographer的建图与 ...