A.2023(字符串)
题意:
给出一个以2023为结尾的字符串,要求你把该字符串的结尾修改为2024。
分析:
本题解法较多,可以先输出前s.length()−1s.length() - 1s.length()−1个字符,然后单独输出4,也可以修改最后一个字符,再整体输出。
代码:
#include<bits/stdc++.h>
using namespace std;
int main(){
string s;
cin >> s;
s[s.size() - 1] = '4';
cout << s << endl;
return 0;
}
B.Tetrahedral Number(枚举)
题意:
给出一个数字NNN,要求输出所有满足x+y+z≤Nx + y + z \le Nx+y+z≤N的三元组(x,y,z)(x, y, z)(x,y,z)
分析:
使用三层循环枚举三元组即可。
代码:
#include<bits/stdc++.h>
using namespace std;
int main(){
int n;
cin >> n;
for (int i = 0; i <= n; i++) {
for (int j = 0; i + j <= n; j++) {
for (int k = 0; i + j + k <= n; k++) {
cout << i << ' ' << j << ' ' << k << endl;
}
}
}
return 0;
}
C.Loong Tracking(思维)
题意:
有nnn个点,开始时第i(i=1,2,...,n)i(i = 1, 2, ..., n)i(i=1,2,...,n)个点的位置为(i,0)(i, 0)(i,0)。
题目将给出QQQ个操作,操作分为以下两种:
-
1 C: 将序号为1的点按C方向移动一格,且所有序号为jjj的格子也会随着移动,即序号为j(j=2,3,...,n)j(j = 2, 3, ..., n)j(j=2,3,...,n)的格子会来到操作前序号为j−1j - 1j−1的格子所在的位置,其中C∈(R,L,U,D)C \in (R, L, U, D)C∈(R,L,U,D),且(R,L,U,D)(R, L, U, D)(R,L,U,D)分别代表右,左,上,下。 -
2 p: 找到当前序号为ppp的点所在的位置,并将该位置输出。
分析:
如果按要求进行模拟,那么时间复杂度就会来到O(NQ)O(NQ)O(NQ),无法通过本题。
观察样例后,可以想到,既然移动的永远只有序号为111的点,那么可以先将所有点所在的位置存在vector中,然后在每次移动操作后将序号为111的点当前所在的位置也存入vector中,那么对于此时对于每个查询,均有一个长度为n+q(q为移动次数)n + q(q\text{为移动次数})n+q(q为移动次数)的vector,且所有点均会沿着前面的点走过的路径进行移动,因此,可以通过序号+移动次数直接得到当前位置所在的下标。
为了便于处理,将序号倒着存放,此时序号为iii的点开始时所在的下标为n−i(下标从0开始存放)n - i(\text{下标从0开始存放})n−i(下标从0开始存放),每次询问需要输出的元素对应的下标为n−i+cntn - i + cntn−i+cnt,其中cntcntcnt为移动操作的次数。
代码:
#include<bits/stdc++.h>
using namespace std;
int n, q;
vector<int> x, y;
void init() {//初始化,将开始时所有点的坐标放入vector
for (int i = n; i >= 1; i--) {
x.push_back(i);
y.push_back(0);
}
}
int main(){
cin >> n >> q;
init();
int cnt = 0;
while (q--) {
int op;
cin >> op;
if (op == 1) {
char c;
cin >> c;
if (c == 'U') {
x.push_back(x.back());
y.push_back(y.back() + 1);
} else if (c == 'D') {
x.push_back(x.back());
y.push_back(y.back() - 1);
} else if (c == 'L') {
x.push_back(x.back() - 1);
y.push_back(y.back());
} else {
x.push_back(x.back() + 1);
y.push_back(y.back());
}
cnt++;
} else {
int id;
cin >> id;
cout << x[n - id + cnt] << ' ' << y[n - id + cnt] << endl;
}
}
return 0;
}
D.Loong and Takahashi(构造)
题意:
给出一个N×NN \times NN×N的网格,其中NNN为奇数,你需要按以下要求在网格内填充内容:
-
字母
T必须在网格正中间。 -
任意一个其他的网格均需填下数字(1∼N2−1)(1 \sim N^{2} - 1)(1∼N2−1)。
-
所有填在网格中的数字,需满足值为iii所在的格子与值为i+1i + 1i+1的网格相邻(四方向)。
分析:
按要求进行模拟,从最外圈开始进行右,下,左,上四方向填数即可。
代码:
#include<bits/stdc++.h>
using namespace std;
int n, ans[50][50];
int check(int x, int y) {
if (x < 1 || x > n || y < 1 || y > n || ans[x][y]) return 0;
return 1;
}
int main(){
cin >> n;
int x = 1, y = 1, cnt = 1;
while (cnt < n * n) {
while (check(x, y)) {
ans[x][y++] = cnt++;
}
y--;
x++;
while (check(x, y)) {
ans[x++][y] = cnt++;
}
x--;
y--;
while (check(x, y)) {
ans[x][y--] = cnt++;
}
y++;
x--;
while (check(x, y)) {
ans[x--][y] = cnt++;
}
x++;
y++;
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
if (j != 1) cout << ' ';
if (ans[i][j] == 0) cout << 'T';
else cout << ans[i][j];
}
cout << endl;
}
return 0;
}
E - Non-Decreasing Colorful Path(并查集,DP)
题意
有一个“无向图”,上面有NNN个结点和MMM条边。每一个结点VVV都有一个对应的权值AVA_VAV。我们希望从第111个结点出发,到达第NNN个结点,在这个过程中,路上的每个点只经过一次,并且尽可能获得更多的分数。获得分数的规则是这样的:
- 在每一次移动时,如果我们要从结点UUU移动到结点VVV,则必须满足AU≤AVA_U\le A_VAU≤AV。
- 如果从111号点到NNN号点的所有路径中,没有任意一条满足这个要求,那么分数即为000。
- 如果存在这样的路径,那么我们观察路径上的点的权值,去重以后的个数即为分数。
以题目中的样例1为例,N=5N=5N=5时,通过1->3->4->5到达NNN号点,这四个点的权值去重后共有4个数,因此分数为4。若某条路径路过444个点,但对应的权值分别为10,10,30,4010,10,30,4010,10,30,40,则分数应为333,因为两个101010重复了。
此处需要注意的是,这个图可能非常大(2≤N≤2×1052\le N \le 2\times10^52≤N≤2×105)。
思路
在到达结点NNN的过程中,路过的点的权值组成的序列,一定是不下降序列,我们的目标是让这个序列去重以后的长度尽可能长,那么我们可以认为,相邻的、权值相同的点可以被合并为同一个点,这个合并的过程可以使用并查集来完成。
在经过这样的压缩以后,整张图就变成了一张有向无环图(DAG)。此时你就可以使用带有备忘录的dfs来解决问题了。我们从111号点出发进行深度优先遍历,并从NNN号结点向前回溯,对于当前结点curcurcur来说,我们用dp[cur]dp[cur]dp[cur]表示从它出发,到达NNN号结点所遍历的节点数(包括curcurcur自己)。
为了计算dp[cur]dp[cur]dp[cur]的值,我们遍历所有他可以到达的结点vvv,dp[cur]=max{dp[v]}+1dp[cur]=\max\{dp[v]\}+1dp[cur]=max{dp[v]}+1,dp[n]=1dp[n]=1dp[n]=1。当然,dp[cur]dp[cur]dp[cur]的值只有在大于000时才有意义。
代码
#include<bits/stdc++.h>
using namespace std;
const int maxn = 2e5 + 10, minn = -1e8;
int n, m, u, v, a[maxn], fa[maxn], dp[maxn], flag;
vector<int> G[maxn];
vector<pair<int, int>> e;
int dfs(int x) {
if (fa[x] == x)return x;
else return fa[x] = dfs(fa[x]);
}
void merge(int x, int y) {
fa[dfs(x)] = dfs(y);
}
void solve(int cur) {
if (dp[cur] != minn)return;
if (cur == dfs(n)) {
//dp[cur]表示从cur出发到n号点,会途径路过多少个点,注意是包括cur的
dp[cur] = 1;
flag = 1; //标记:存在这样的道路
return;
}
for (auto v: G[cur]) {
solve(v);
dp[cur] = max(dp[cur], dp[v] + 1);
}
}
int main() {
cin >> n >> m;
for (int i = 1; i <= n; i++) {
cin >> a[i];
fa[i] = i;
dp[i] = minn;
}
for (int i = 1; i <= m; i++) {
cin >> u >> v;
if (a[u] == a[v])merge(u, v); // 此时u和v可以看成同一个点,进行合并
else e.push_back(make_pair(u, v));
}
for (auto p: e) {
// 必须先处理合并,再去加入其他的边
u = p.first;
v = p.second;
if (a[u] < a[v])G[dfs(u)].push_back(dfs(v));
else G[dfs(v)].push_back(dfs(u));
}
flag = 0;
solve(dfs(1));
if (flag) cout << dp[dfs(1)] << endl;
else cout << 0 << endl;
return 0;
}
F - Hop Sugoroku(DP)
题意
有nnn个数A1,A2,…,AnA_1, A_2, \dots, A_nA1,A2,…,An,对应nnn个正方形。起初,你把棋子放在111号格子的位置,接下来你可以重复以下步骤:
- 棋子可以从第iii个位置移动到第i+Ai×xi+A_i\times xi+Ai×x的位置,即:若你在第iii个位置,你就可以向后移动任意AiA_iAi的xxx倍。xxx可以是任意一个正整数,但这个位置不能超过NNN。
- 你可以在任意一个位置停下来。
现在问你,你最多可以走多少种不同的路线?答案需要对998244353取模。
思路
题目中,AAA数组中所有的元素都是正整数,所以移动的过程方向不变。一个简单的思路是用dp[x]dp[x]dp[x]表示从xxx出发,向后的路线条数,那么我们可以用伪代码表示:
dp[1] = 1; //初始化为1
for (int i = 1; i <= n; i++) {
for (int j = i + A[i]; j <= n; j += A[i]) {
dp[j] = (dp[j] + dp[i]) % mod; //从i出发可以到达j,因此到达j的方案数应当被更新
}
}
最后,∑i=1ndp[i]\sum\limits_{i=1}^{n}dp[i]i=1∑ndp[i]对mod取模即为最终答案。当AAA数组中的数很大的时候,这个算法的时间复杂度接近线性级别,但如果数组中的数都不大时,这个算法的时间复杂度就是平方级别,而本题数据量也很大(1≤N≤2×1051\le N \le 2\times 10^51≤N≤2×105),这样的方法显然是无法通过所有样例的。
一个可能的思路是采用分治,根据A数组中元素的大小分开讨论,若A[i]A[i]A[i]很大,就按照上面的循环直接进行。否则,若A[i]A[i]A[i]比较小,我们利用dp[i]=∑i≡j(modAj)dp[j]dp[i]=\sum\limits_{i\equiv j\pmod{A_j}}dp[j]dp[i]=i≡j(modAj)∑dp[j]这一性质,维护一个fff数组,f[x][y]f[x][y]f[x][y]表示:所有对yyy取模,得到的结果是xxx的元素都需要标记的值。
那么,怎么区分所谓的“很大”和“比较小”呢?这里我们以n\sqrt{n}n为分界线,根据数组的大小可以知道,时间复杂度为O(nn)O(n\sqrt{n})O(nn)。这里的n\sqrt{n}n可以根据每个输入值灵活改变,也可以根据nnn可能的最大值,将分界线直接设定为500500500(约等于2×105\sqrt{2\times10^5}2×105)。
代码
#include<bits/stdc++.h>
using namespace std;
const int maxn = 2e5 + 10, maxm = 510, mod = 998244353;
int a[maxn], dp[maxn], f[maxm][maxm];
int main() {
int n;
cin >> n;
int m = sqrt(n);
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
dp[1] = 1;
int res = 0;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
// 加上之前累计的标记值
dp[i] = (dp[i] + f[j][i % j]) % mod;
}
//根据当前的结果向后推算
if (a[i] > m) {
//相对比较大,直接推即可
for (int j = i + a[i]; j <= n; j += a[i]) {
dp[j] = (dp[j] + dp[i]) % mod;
}
} else {
f[a[i]][i % a[i]] += dp[i];
f[a[i]][i % a[i]] %= mod;
}
res = (res + dp[i]) % mod; //统计结果
}
cout << res << endl;
return 0;
}
学习交流
以下为学习交流QQ群,群号: 546235402,每周题解完成后都会转发到群中,大家可以加群一起交流做题思路,分享做题技巧,欢迎大家的加入。

1496

被折叠的 条评论
为什么被折叠?



