冶炼金属
问题描述
小蓝有一个神奇的炉子用于将普通金属冶炼成为一个特殊金属
。
这个炉子有一个称作转换率的属性,
是一个正整数,这意味着消耗
个普通金属
恰好可以冶炼出一个特殊金属
,当普通金属
的数目不足
时,无法继续冶炼。
现在给出了条冶炼记录,每条记录中包含两个整数
和
,这表示本次投入了
个普通金属
,最终冶炼出了
个特殊金属
。
每条记录都是独立的,这意味着上一次没消耗完的普通金属不会累加到下一次的冶炼当中。
根据这条冶炼记录,请你推测出转换率
的最小值和最大值分别可能是多少,题目保证评测数据不存在无解的情况。
输入格式
第一行一个整数,表示冶炼记录的数目。
接下来输入行,每行两个整数
、
,含义如题目所述。
输出格式
输出两个整数,分别表示可能的最小值和最大值,中间用空格分开。
数据范围
对于 30% 的评测用例,。
对于 60% 的评测用例,。
对于 100% 的评测用例,,
。
输入样例:
3
75 3
53 2
59 2
输出样例:
20 25
问题分析
由样例数据范围可知,最多接受 的复杂度。
根据题意可知,满足式,其中
为每次冶炼剩余的普通金属。因为能炼出
个,而炼不出
个特殊金属,所以满足
且
,即有
。最小值为
,最大值为
。
求解代码
#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 1e4 + 10;
int n;
int A[N], B[N];
int main()
{
cin >> n;
for (int i = 0; i < n; i++) scanf("%d%d", &A[i], &B[i]);
// vmax ≥ a / b, vmin < a / (b + 1)
int vmax = A[0] / B[0], vmin = A[0] / (B[0] + 1) + 1;
for (int i = 1; i < n; i++)
{
int tmax = A[i] / B[i], tmin = A[i] / (B[i] + 1) + 1;
vmax = min(vmax, tmax);
vmin = max(vmin, tmin);
}
cout << vmin << ' ' << vmax << endl;
return 0;
}
飞机降落
问题描述
有 架飞机准备降落到某个只有一条跑道的机场。
其中第 架飞机在
时刻到达机场上空,到达时它的剩余油料还可以继续盘旋
个单位时间,即它最早可以于
时刻开始降落,最晚可以于
时刻开始降落。
降落过程需要 个单位时间。
一架飞机降落完毕时,另一架飞机可以立即在同一时刻开始降落,但是不能在前一架飞机完成降落前开始降落。
请你判断 架飞机是否可以全部安全降落。
输入格式
输入包含多组数据。
第一行包含一个整数 ,代表测试数据的组数。
对于每组数据,第一行包含一个整数 。
以下 行,每行包含三个整数:
,
和
。
输出格式
对于每组数据,输出 YES 或者 NO,代表是否可以全部安全降落。
数据范围
对于 30% 的数据,。
对于 100% 的数据,,
,
。
输入样例:
2
3
0 100 10
10 10 10
0 2 20
3
0 10 20
10 10 20
20 10 20
输出样例:
YES
NO
问题分析
由题意可知道最多有10架飞机需要安排,属于较为明显的组合优化问题。需要考虑10架飞机的降落顺序,根据数学中排列知识可知方案总数为 个,时间复杂度能够满足条件。
常见的排列数问题可以使用 dfs 进行求解。
求解代码
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 15;
int n, flag;
int T[N], D[N], L[N];
bool st[N];
void dfs(int u, int t) // u为当前是第几架降落的飞机,t为当前时间
{
if (u == n) flag = true;
if (flag) return;
for (int i = 0; i < n; i++)
{
if (!st[i])
{
if (t > T[i] + D[i]) return; // 可行性剪枝
int tt = t;
st[i] = true; // 保护现场
if (t < T[i]) t = T[i];
t += L[i];
dfs(u + 1, t);
st[i] = false; // 恢复现场
t = tt;
}
}
}
int main()
{
int t;
cin >> t;
while (t--)
{
cin >> n;
for (int i = 0; i < n; i++) cin >> T[i] >> D[i] >> L[i];
flag = false;
dfs(0, 0);
if (flag) puts("YES");
else puts("NO");
}
return 0;
}
岛屿个数
问题描述
小蓝得到了一副大小为 的格子地图,可以将其视作一个只包含字符
0
(代表海水)和 1
(代表陆地)的二维数组,地图之外可以视作全部是海水,每个岛屿由在上/下/左/右四个方向上相邻的 1
相连接而形成。
在岛屿 所占据的格子中,如果可以从中选出
个不同的格子,使得他们的坐标能够组成一个这样的排列:
,其中
是由
通过上/下/左/右移动一次得来的
,此时这
个格子就构成了一个 “环”。
如果另一个岛屿 所占据的格子全部位于这个 “环” 内部,此时我们将岛屿
视作是岛屿
的子岛屿。
若 是
的子岛屿,
又是
的子岛屿,那
也是
的子岛屿。
请问这个地图上共有多少个岛屿?
在进行统计时不需要统计子岛屿的数目。
输入格式
第一行一个整数 ,表示有
组测试数据。
接下来输入 组数据。
对于每组数据,第一行包含两个用空格分隔的整数 、
表示地图大小;接下来输入
行,每行包含
个字符,字符只可能是
0
或 1
。
输出格式
对于每组数据,输出一行,包含一个整数表示答案。
数据范围
对于 30% 的评测用例,。
对于 100% 的评测用例,。
输入样例:
2
5 5
01111
11001
10101
10001
11111
5 6
111111
100001
010101
100001
111111
输出样例:
1
3
样例解释
对于第一组数据,包含两个岛屿,下面用不同的数字进行了区分:
01111
11001
10201
10001
11111
岛屿 2 在岛屿 1 的 “环” 内部,所以岛屿 2 是岛屿 1 的子岛屿,答案为 1。
对于第二组数据,包含三个岛屿,下面用不同的数字进行了区分:
111111
100001
020301
100001
111111
注意岛屿 3 并不是岛屿 1 或者岛屿 2 的子岛屿,因为岛屿 1 和岛屿 2 中均没有“环”。
问题分析
经典的 bfs 问题,最多仅需遍历整张地图,故时间复杂度为。
利用 数组来存储地图,在读入的过程中需使用到字符串处理,
数组来记录是否被访问过。
问题描述中写的较为复杂难懂,实际表达的意思即为海水朝周围8个方向进行搜索,而陆地朝周围4个方向进行搜索。
在读入地图时,从 开始读入,将
数组的外围视为外海,此时遍历的第一个节点一定为海水。
(此题没有过多思考,按照模板来书写)
求解代码
#include<iostream>
#include<cstring>
#include<queue>
#include<algorithm>
#define x first
#define y second
using namespace std;
typedef pair<int, int> PII;
const int N = 60;
int m, n;
int g[N][N];
bool st[N][N];
void bfs_1(int sx, int sy)
{
queue<PII> q;
q.push({sx, sy});
st[sx][sy] = true;
while(q.size())
{
PII t = q.front();
q.pop();
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, -1, 0, 1};
for (int i = 0; i < 4; i++)
{
int nx = t.x + dx[i], ny = t.y + dy[i];
if (nx < 0 || nx > m + 1 || ny < 0 || ny > n + 1) continue; // 出界
if (st[nx][ny]) continue; // 已被访问
if (g[nx][ny] == 0) continue;
// 入队并标记
q.push({nx, ny});
st[nx][ny] = true;
}
}
return;
}
void bfs_0(int sx, int sy, int &res)
{
queue<PII> q;
q.push({sx, sy});
st[sx][sy] = true;
while (q.size())
{
// 取队首
PII t = q.front();
q.pop();
// 后继节点入队
int dx[8] = {0, 1, 1, 1, 0, -1, -1, -1}, dy[8] = {-1, -1, 0, 1, 1, 1, 0, -1};
for (int i = 0; i < 8; i++)
{
int nx = t.x + dx[i], ny = t.y + dy[i];
if (nx < 0 || nx > m + 1 || ny < 0 || ny > n + 1) continue; // 出界
if (st[nx][ny]) continue; // 已被访问
if (g[nx][ny] == 1)
{
bfs_1(nx, ny), res++;
continue;
}
// 入队并标记
q.push({nx, ny});
st[nx][ny] = true;
}
}
return;
}
int main()
{
int T;
cin >> T;
while (T--)
{
cin >> m >> n;
// 重置地图和状态
memset(g, 0, sizeof g);
memset(st, false, sizeof st);
// 读入地图
for (int i = 1; i <= m; i++)
for (int j = 1; j <= n; j++)
{
char c; cin >> c;
g[i][j] = c - '0';
}
// bfs
int res = 0;
bfs_0(0, 0, res);
cout << res << endl;
}
return 0;
}
子串简写
问题描述
程序猿圈子里正在流行一种很新的简写方法:
对于一个字符串,只保留首尾字符,将首尾字符之间的所有字符用这部分的长度代替。
例如 internationalization 简写成 i18n, Kubernetes 简写成 K8s,Lanqiao 简写成 L5o。
在本题中,我们规定长度大于等于 的字符串都可以采用这种简写方法(长度小于
的字符串不配使用这种简写)。
给定一个字符串 和两个字符
和
,请你计算
有多少个以
开头
结尾的子串可以采用这种简写?
输入格式
第一行包含一个整数 。
第二行包含一个字符串 和两个字符
和
。
输出格式
一个整数代表答案。
数据范围
对于 20% 的数据,。
对于 100% 的数据,。
只包含小写字母。
和
都是小写字母。
代表字符串
的长度。
输入样例:
4
abababdb a b
输出样例:
6
问题分析
由数据样例范围可知,时间复杂度不能超过 。
首先,容易想到最朴素(暴力)的做法:两重for循环判断所有子串,复杂度达到了 必然是会TLE的。当然,在比赛的时候也能通过一些小样例拿到一定的分数。
接下来,需要思考如何对朴素做法进行优化。这里使用双指针动态维护一段长度为 k 的区间,在右端点往后用前缀和思想预处理 的数量。从而将时间复杂度降低到
。
求解代码
#include<iostream>
#include<cstring>
using namespace std;
const int N = 50010;
int k;
char c1, c2;
string s;
int main()
{
cin >> k >> s >> c1 >> c2;
long long cnt = 0, res = 0;
for (int i = 0, j = k - 1; s[j]; i++, j++)
{
if (s[i] == c1) cnt++;
if (s[j] == c2) res += cnt;
}
cout << res << endl;
return 0;
}