Z Z U L I 春季 a c m 选拔赛 ( 三 ) ZZULI 春季acm选拔赛 (三) ZZULI春季acm选拔赛(三)
- 以下所有代码都只提供核心部分 (由于代码是之后重新打的, 细节错了的话可以提醒我改一下
- 由于上次的宏定义太多了, 有同学反应看不懂, 这次就全都去掉了
- 看不懂的语法记得先查一下, 查不到的或者有疑问的可以问我
7-1 字符串连接
- 因为
单词1
和单词 2
各自含有的单词不重复, 即从单词1
和单词2
中任意选一对, 都能组成不同的语言 即答案为n * m
void solve() {
int n, m;
cin >> n >> m;
string s;
// 由于是多实例, 记得把所有的输入读完, 否则会影响下一次读入
for (int i = 1; i <= n + m; i ++) cin >> s;
cout << n * m;
}
7-2 回文素数
- 从
n
循环到m
逐个判断即可, 但由于判断素数需要 O ( n ) O(\sqrt{n}) % O(n)的复杂度, 而判断回文大概只需要 O ( l e n ( n ) ) O(len(n)) O(len(n))的复杂度 - 故我们先判断该数是否回文, 再判断该数是否是素数
bool isH(int x) {
string s = to_string(x); // c++ 11之后才支持该函数
string t = s;
reverse(s.begin(), s.end());
return s == t;
}
bool isP(int x) {
// 由于 x 至少是 3 位数, 不再特判 1
for (int i = 2; i <= x / i; i ++)
if (x % i == 0)
return false;
return true;
}
void solve() {
int n, m;
cin >> n >> m;
vector<int> ans;
for (int i = n; i <= m; i ++)
if (isH(i))
if (isP(i))
ans.push_back(i);
cout << ans.size() << '\n';
for (auto x: ans) cout << x << "\n";
}
7-3 轰炸
- 一眼差分, 再看一眼就不会了, 结果直接暴力就可以过 (hh
- 轰炸次数暴力累加即可, 开个vis数组存一下(x, y)最后一次轰炸的编号即可
const int N = 1000 + 10;
int n, m, k, q;
int a[N][N];
int vis[N][N];
void solve() {
cin >> n >> m >> k >> q;
for (int c = 1; c <= k; c ++) {
int x1, y1, x2, y2;
cin >> x1 >> y1 >> x2 >> y2;
for (int i = x1; i <= x2; i ++)
for (int j = y1; j <= y2; j ++)
a[i][j] ++, vis[i][j] = c;
}
while (q --) {
int x, y;
cin >> x >> y;
if (a[x][y]) cout << "Y " << a[x][y] << " " << vis[x][y] << "\n";
else cout << "N\n";
}
}
7-4 溢出控制
- 没想到现在还有人考高精度 (不是很懂
- 题目已经把答案说的很明显了, 答案是 2 8 ∗ m − 1 − 1 2^{8*m - 1} -1 %power 28∗m−1−1, 但这个数很大, 需要用高精度乘法来算, 不知道double会不会溢出, 没试, 各位可以试一下
- 也可以直接用
python
vector<int> mul(vector<int> A, int b) { // 我的高精度乘法的模板, 看不懂的可以上网学一下
vector<int> C;
int t = 0;
for (int i = 0; i < A.size(); i ++) {
int x = A[i] * b + t;
C.push_back(x % 10);
t = x / 10;
}
while (t) C.push_back(t % 10), t /= 10;
return C;
}
void solve() {
int m;
cin >> m;
int n = m * 8 - 1; // 2的幂次
vector<int> ans(1, 1);
for (int i = 1; i <= n; i ++) ans = mul(ans, 2);
ans[0] --; // 2的任何次方最后一位都不为 0, 即使 -1, 也不会从前面借位
reverse(ans.begin(), ans.end()); // 答案是倒序存的, 需要反过来输出
for (auto x: ans) cout << x;
cout << "\n"; // 多实例记得换行
}
- py还是不太会用, 查了半天 (晕
# 方法 1
import sys
for n in sys.stdin:
m = int(n) * 8 - 1
print(2 ** m - 1)
# 方法 2
while True:
try:
n = input()
m = int(n) * 8 - 1
print(2 ** m - 1)
except:
break
7-5 高空坠球
- 模拟一下即可
- 除了第一次落下来总高度加上 h, 以后每一次落地加的都是2倍的 h
void solve() {
int k;
double h, sum = 0;
cin >> h >> k;
if (k) sum += h; // 特判k == 0的情况
else h = 0;
h /= 2;
for (int i = 1; i < k; i ++) sum += 2 * h, h /= 2;
printf("%.1f %.1f", sum, h);
}
7-6 Keven的字符串
- 典型贪心问题 (时间原因我不再证明, 感兴趣可以查一下
贪心之数字拼接
- 结论: 按特殊规则(有的难描述, 看代码吧)排序之后, 直接拼接就是答案
bool cmp(string a, string b) {
return a + b < b + a;
}
void solve() {
cin >> n;
vector<string> s1(n);
for (auto& s: s1) cin >> s;
// 方法 1
sort(s1.begin(), s1.end(), [&](string a, string b) { // 不会写lambda的可以手写个cmp函数, 见下面的sort
return a + b < b + a;
});
// 方法 2
sort(s1.begin(), s1.end(), cmp);
for (auto s: s1) cout << s;
}
7-7 放文件
- 考虑贪心, 模拟一下就行 (看数据范围应该是要开long long的, 我的已经 #define int long long
- 开个c数组来存放a[i] - b[i]的值, 即压缩 i 可以获得的空间, 对 c 从大到小排序, 依次判断即可
const int N = 1e5 + 10;
int n, m;
int a[N], b[N], c[N];
void solve() {
int sum = 0;
cin >> n >> m;
for (int i = 1; i <= n; i ++) {
cin >> a[i] >> b[i];
sum += a[i];
c[i] = a[i] - b[i];
}
sort(c + 1, c + n + 1, greater<int>());
for (int i = 0; i <= n; i ++) { // 从 0 开始判断, 有可能不需要压缩
if (sum - c[i] <= m) {
cout << i;
return;
}
sum -= c[i];
}
cout << "-1"; // 如果最后还没成功, 就输出 -1
}
7-8 编辑距离问题
- 字符串DP典题
- 由于时间有限, 不再放思路, 对这题感兴趣的可以私下找我聊
const int N = 2000 + 10;
int n, m;
int f[N][N];
void solve() {
string a, b;
cin >> a >> b;
n = a.size(), m = b.size();
a = " " + a; b = " " + b; // 让字符串下标从 1 开始, 方便判断
memset(f, 0x3f, sizeof f);
for (int i = 0; i <= n; i ++) f[i][0] = i;
for (int i = 0; i <= m; i ++) f[0][i] = i;
for (int i = 1; i <= n; i ++)
for (int j = 1; j <= m; j ++) {
if (a[i] == b[j]) f[i][j] = f[i - 1][j - 1];
else f[i][j] = min({f[i][j - 1] + 1, f[i - 1][j] + 1, f[i - 1][j - 1] + 1});
}
cout << f[n][m];
}
7-9 模拟炸弹人
- 考察
flood fill
算法, 又名洪水灌溉算法 - (感觉并查集也行, 有时间的同学可以试试
DFS
和BFS
都可以实现- 更新一下, 测试发现
DFS
会爆空间, 1000 * 1000 的矩阵如果全是*
那么应该是会爆栈的, 所以写BFS
还是稳妥一点
BFS
写法
const int N = 1000 + 10, M = 2 * N;
int n, m;
int k, q;
char g[N][N];
void bfs(int i ,int j) {
queue<pair<int, int>> q;
q.push({i, j});
g[i][j] = '0';
int dx[] = {-1, 0 ,1, 0}, dy[] = {0, 1, 0, -1};
while (q.size()) {
auto [x, y] = q.front(); q.pop();
for (int c = 1; c <= k; c ++) {
for (int i = 0; i < 4; i ++) {
int a = x + c * dx[i], b = y + c * dy[i];
if (a < 1 || a > n || b < 1 || b > m) continue;
if (g[a][b] == '*') {
g[a][b] = '0';
q.push({a, b});
}
}
}
}
}
void solve() {
cin >> n >> m >> k;
for (int i = 1; i <= n; i ++)
for (int j = 1; j <= m; j ++)
cin >> g[i][j];
int cnt = 0;
for (int i = 1; i <= n; i ++)
for (int j = 1; j <= m; j ++)
if (g[i][j] == '*') {
bfs(i, j);
cnt ++;
}
cout << cnt;
}
DFS
写法 (只能拿20分
const int N = 1000 + 10;
int n, m, k;
char g[N][N];
int dx[] = {-1, 0 ,1, 0}, dy[] = {0, 1, 0, -1};
void dfs(int x ,int y) {
g[x][y] = '0';
for (int c = 1; c <= k; c ++) {
for (int i = 0; i < 4; i ++) {
int a = x + c * dx[i], b = y + c * dy[i];
if (a < 1 || a > n || b < 1 || b > m) continue;
if (g[a][b] == '*') dfs(a, b);
}
}
}
void solve() {
cin >> n >> m >> k;
for (int i = 1; i <= n; i ++)
for (int j = 1; j <= m; j ++)
cin >> g[i][j];
int cnt = 0;
for (int i = 1; i <= n; i ++)
for (int j = 1; j <= m; j ++)
if (g[i][j] == '*') {
dfs(i, j);
cnt ++;
}
cout << cnt;
}
7-10 会议安排
- 贪心
- 跟上次选拔赛那个贪心一模一样, 不多证明
- 结论: 按结束时间从小到大排序, 然后从前到后依次安排即可
int n, m;
void solve() {
cin >> n;
vector<pair<int, int>> ac(n);
for (auto& [en, be]: ac) cin >> be >> en;
sort(ac.begin(), ac.end());
int cnt = 1;
int las = ac[0].first;
for (int i = 1; i < ac.size(); i ++) {
auto [en, be] = ac[i];
if (be >= las) {
las = en;
cnt ++;
}
}
cout << cnt << "\n";
}