比赛链接

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. java接口多实现注入方法总结

    1. 单实现接口注入方法 1.1 构造注入(推荐) @RequiredArgsConstructor public class TestController { // 其只有一个具体的实现类 priv ...

  2. 一次XGBoost性能优化-超线程影响运算速度

    一.问题背景 一个朋友在使用 XGBoost 框架进行机器学习编码,他们的一个demo, 在笔记本的虚拟机(4核)运行的时候,只要8s, 但是在一个64核128G 的物理机上面的虚拟机去跑的时候,发现 ...

  3. Citrix Virtual Apps and Desktops 7 2203 LTSR虚拟云桌面单机教程

    哈喽大家好,欢迎来到虚拟化时代君(XNHCYL). 大家好,我是虚拟化时代君,一位潜心于互联网的技术宅男.这里每天为你分享各种你感兴趣的技术.教程.软件.资源.福利--(每天更新不间断) 一 .主要内 ...

  4. Lucene从入门到实战

    Lucene 在了解Lucene之前,我们先了解下全文数据查询. 全文数据查询 我们的数据一般分为两种:结构化数据和非结构化数据 结构化数据:有固定格式或有限长度的数据,如数据库中的数据.元数据 非结 ...

  5. 我所使用的生产 Java 17 启动参数

    JVM 参数升级提示工具:jacoline.dev/inspect JVM 参数词典:chriswhocodes.com Revolut(英国支付巨头)升级 Java 17 实战:https://ww ...

  6. 11.Firewalld防火墙

    Firewalld防火墙 目录 Firewalld防火墙 Firewalld概述 Firewalld Firewalld和iptables的关系 netfilter Firewalld/iptable ...

  7. 【Java面试】@Resource 和 @Autowired 的区别

    Hi,大家好,我是Mic. 一个工作2年的粉丝,问我一个Spring里面的问题. 希望我能从不同的视角去分析,然后碾压面试官. 这个问题是: "@Resource和@Autowired&qu ...

  8. python基础知识-day9(数据驱动)

    1.数据驱动的概念 在自动化测试中,需要把测试的数据分离到JSON,YAML等文件中. 2.YAML 的相关知识 YAML 入门教程 分类 编程技术 YAML 是 "YAML Ain't a ...

  9. linux web漏洞扫描arachni

    1. 下载arachni https://www.arachni-scanner.com/download/下载Linux x86 64bit 2. 上次解压直接使用 tar xzf arachni- ...

  10. Android (微信扫码登录) 获取微信二维码+扫码登录

    话不多说  直接上菜! 一.因为是微信扫码登录,所有要在微信开放平台  微信开放平台 (qq.com) 进行注册----- 如下 1.资源中心 里面也有详细的官方讲解,里面也有demo  可以下载 2 ...