知识总结
转化、构造、模拟。
转化:将算法转化为其他形式。
构造:通过算法构造一个模型。
模拟:通过算法模拟一个过程。
随堂练习
T1 排行榜
题目描述
https://www.luogu.com.cn/problem/P1159
思路解析
显然这题可以直接贪心。把一首一首歌往排行榜上放。
对于 SAME 的歌,直接放在原位。UP 的尽量往后放,DOWN 的尽量往前。由于题目保证有一解,所以同为 UP 或 DOWN 的相对位置不需发生变化。所以扫一遍就可以了。
代码实现
#include <bits/stdc++.h>
using namespace std;
const int N = 1e3 + 5;
string cina, cinb;
int n, up, down, now1, now2;
string Sup[N], Sdown[N], ans[N];
inline void init(int id) {
if (cinb == "UP") {
Sup[++up] = cina;
} else if (cinb == "DOWN") {
Sdown[++down] = cina;
} else {
ans[id] = cina;
}
return;
}
signed main() {
scanf("%d", &n);
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
for (int i = 1; i <= n; i++) {
cin >> cina >> cinb;
init(i);
}
for (int i = 1; i <= n; i++) {
if (ans[i] == "") {
if (now1 < down) {
ans[i] = Sdown[++now1];
} else {
ans[i] = Sup[++now2];
}
}
}
for (int i = 1; i <= n; i++) {
cout << ans[i] << "\n";
}
return 0;
}
T2 左右构造机
给你一个数组 S,包含 n 个元素,它们构成一个环,
$S_0$ 的左边是 $S_{n-1}$ ,$S_n-1$ 的右边是 $S_0$ 。
再给你一个同样长度的目标数组 T,每次你可以对 S 做的操作有如下两种:
L:每个数都加上左边的数
R:每个数都加上右边的数
以上两种加法对于每个数都是同时完成的,即每次操作完后数组中的某个数是本次操作前数组中的两个数之和。
输出一个可以使得 $S$ 变成 $T$ 的操作序列,序列长度不能超过 $100$。
思路解析
1:由于无论什么操作每次总和都会乘以 2,所以总的操作次数 sum 可以根据总和的变化来确定
2:观察到 L 操作与 R 操作产生的序列
a0 a1 a2 a3
L 操作产生 a0+a3 a1+a0 a2+a1 a3+a2
R 操作产生 a0+a1 a1+a2 a2+a3 a3+a0
相邻两个数都会相加一次,唯一的区别是所有的数瞬移了一个位置
所以可以先进行 sum 次 L 操作,然后再判断需要移动几次到达 t 数组
代码实现
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e4 + 10;
bool flag = true;
int n, s[N], t[N], c[N], sum, tum, cnt, f = 1, ans;
inline void input() {
scanf("%lld", &n);
for (int i = 1; i <= n; i++) {
scanf("%lld", &s[i]);
sum += s[i];
}
for (int i = 1; i <= n; i++) {
scanf("%lld", &t[i]);
tum += t[i];
}
return;
}
inline void check() {
for (int i = 1; i <= n; i++) {
if (s[i] != t[i]) {
flag = false;
break;
}
}
if ((sum == 0 && tum == 0) || flag) {
printf("null\n");
exit(0);
}
if ((sum == 0 && tum != 0)) {
printf("No solution\n");
exit(0);
}
return;
}
inline void solve() {
s[n + 1] = s[1];
for (;;) {
++cnt;
if (sum * 2 > tum) {
flag = true;
break;
} else if (sum * 2 == tum) {
break;
} else {
sum *= 2;
}
}
if (flag) {
printf("No solution\n");
exit(0);
}
for (int i = 1; i <= cnt; i++) {
for (int j = 1; j <= n; j++) {
c[j] = s[j] + s[j + 1];
}
c[n + 1] = c[1];
for (int j = 1; j <= n + 1; j++) {
s[j] = c[j];
}
}
for (;;) {
flag = false;
for (int i = 1; i <= n; i++) {
if (c[i] != t[i]) {
flag = true;
break;
}
}
if (!flag) {
break;
}
ans++;
for (int i = n + 1; i >= 2; i--) {
c[i] = s[i - 1];
}
c[1] = c[n + 1];
for (int i = 1; i <= n + 1; i++) {
s[i] = c[i];
}
if (ans > cnt) {
printf("No solution\n");
exit(0);
}
}
return;
}
inline void output() {
for (int i = 1; i <= ans; i++) {
printf("L");
}
for (int i = 1; i <= cnt - ans; i++) {
printf("R");
}
return;
}
signed main() {
input();
check();
solve();
output();
return 0;
}
T3 回文构造机
题目描述
XTX 非常喜欢回文串,他认为回文会给自己带来好运,有一天他看到一个字符串突发奇想,如果将这个字符串所有字符打乱,然后每次操作只能挑选若干个能构成回文串的字符组合成一个字符串,XTX 最少需要操作几次才能取完所有字符。
思路解析
回文串的个数是由有多少种奇数个数的字符来决定的,每次可以将奇数次的字符放在中间,其他字符往两边放就可以了
代码实现
#include <bits/stdc++.h>
using namespace std;
string s;
int c, i, k, times, cnt[128];
int main() {
cin >> s;
for (int i = 0; i < s.size(); i++) {
cnt[s[i]]++;
}
for (i = 0; i < 128; i++) {
if (cnt[i] % 2 > 0) {
times++;
}
}
if (!times) {
times = 1;
}
printf("%d\n", times);
for (i = 127; i >= 0; i--)
for (int j = 0; j < cnt[i] / 2; j++)
printf("%c", i);
for (k = 0; k < 128 && cnt[k] % 2 == 0; k++);
if (k < 128)
putchar(k++);
for (i = 0; i < 128; i++)
for (int j = 0; j < cnt[i] / 2; j++)
putchar(i);
for (; k < 128; k++)
if (cnt[k] % 2 > 0) {
putchar('\n');
putchar(k);
}
return 0;
}
T4 LCS 构造机
题目描述
一个字符串的子序列可以通过删除这个字符串的若干个字符得到,两个字符串的最长公共子序列是这两个字符串所有的公共子序列中最长的一个,比如 lcs("101","111000")=2,f("101","11011")=3,f("00","1111")=0,给你三个正整数,ab,bc,ca,请找出三个字符串 A,B,C 满足以下条件
-
每一个字符串只包含 0 或者 1
-
每个字符串的长度是 1 到 1000
-
lcs(A, B) = ab, lcs(B, C) = bc, lcs(C, A) = ca
求满足条件的任意一组 A,B,C
思路解析
我们尝试着先去满足其中两个条件
比如 A 与 B 可以先选择 ab 个 0
A = 0000...000(ab 个 0)
B = 0000...000(ab 个 0)
B 与 C 可以选择 bc 个 1 作为 lcs
那么 B 就变成
B = 000...000111...111(ab 个 0,bc 个 1)
C = 1111.....11(bc 个 1)
接下来我们观察 A C 的 LCS 为 0,要使得 A C 的 LCS 为 ac,可以在 A 和 C 后面添加 ac 个 0 或者 ac 个 1,添加之后不能影响 AB BC 之间 的 LCS
假设现在添加了 ac 个 0
那么
A = 0000...000(ab 个 0)
B = 000...000111...111(ab 个 0,bc 个 1)
C = 1111.....11000...000(bc 个 1,ac 个 0)
要想不破坏之前的 lcs 情况,要满足 ac<=bc,ac<=ab,因此找到三个整数中最小的那个就可以构造出答案了
代码实现
#include <bits/stdc++.h>
#define int long long
using namespace std;
vector<string> solve(int ab, int bc, int ca) {
if (ca <= ab && ca <= bc) {
vector<string> ret;
ret.push_back(string(ab, '0'));
ret.push_back(string(ab, '0') + string(bc, '1'));
ret.push_back(string(bc, '1') + string(ca, '0'));
return ret;
}
vector<string> ret = solve(bc, ca, ab);
return {ret[2], ret[0], ret[1]};
}
void construct(int ab, int bc, int ca) {
vector<string> r = solve(ab, bc, ca);
cout << r[0] << endl
<< r[1] << endl
<< r[2] << endl;
}
signed main() {
int ab, ac, bc;
cin >> ab >> bc >> ac;
construct(ab, bc, ac);
return 0;
}
T5 奇数幻方
https://www.luogu.com.cn/problem/P2615
题目描述
N阶奇数幻方是指 $NN$的矩阵,里面包含 $1-NN$这 $N*N$ 个数字。
幻方的每一行每一列对角线的和都是相等的。
给出 $N$,输出一种 $N$ 阶幻方的方案。
思路解析
考虑幻方的通解情况求解
当 $N$ 为奇数时,我们可以通过下方法构建一个幻方:
首先将 $1$ 写在第一行的中间。
之后,按如下方式从小到大依次填写每个数 $K \ (K=2,3,\cdots,N \times N)$ :
- 若 $(K-1)$ 在第一行但不在最后一列,则将 $K$ 填在最后一行, $(K-1)$ 所在列的右一列;
- 若 $(K-1)$ 在最后一列但不在第一行,则将 $K$ 填在第一列, $(K-1)$ 所在行的上一行;
- 若 $(K-1)$ 在第一行最后一列,则将 $K$ 填在 $(K-1)$ 的正下方;
- 若 $(K-1)$ 既不在第一行,也不在最后一列,如果 $(K-1)$ 的右上方还未填数,则将 $K$ 填在 $(K-1)$ 的右上方,否则将 $K$ 填在 $(K-1)$ 的正下方。
代码实现
#include <bits/stdc++.h>
using namespace std;
const int N = 1e3 + 5;
int n, x, y, a[N][N];
signed main() {
scanf("%d", &n);
x = (n + 1) / 2, y = 1;
a[x][y] = 1;
for (int i = 2; i <= n * n; i++) {
if (x != n && y == 1) {
x++;
y = n;
} else if (x == n && y != 1) {
x = 1;
y--;
} else if (x == n && y == 1) {
y++;
} else if (x != n && y != 1) {
if (a[x + 1][y - 1]) {
y++;
} else {
x++;
y--;
}
}
a[x][y] = i;
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
printf("%d ", a[j][i]);
}
printf("\n");
}
return 0;
}