题目一
我叫王大锤,是一家出版社的编辑。我负责校对投稿来的英文稿件,这份工作非常烦人,因为每天都要去修正无数的拼写错误。但是,优秀的人总能在平凡的工作中发现真理。我发现一个发现拼写错误的捷径:
- 三个同样的字母连在一起,一定是拼写错误,去掉一个的就好啦:比如 helllo -> hello
- 两对一样的字母(AABB型)连在一起,一定是拼写错误,去掉第二对的一个字母就好啦:比如 helloo -> hello
- 上面的规则优先“从左到右”匹配,即如果是AABBCC,虽然AABB和BBCC都是错误拼写,应该优先考虑修复AABB,结果为AABCC
我特喵是个天才!我在蓝翔学过挖掘机和程序设计,按照这个原理写了一个自动校对器,工作效率从此起飞。用不了多久,我就会出任CEO,当上董事长,迎娶白富美,走上人生巅峰,想想都有点小激动呢!
……
万万没想到,我被开除了,临走时老板对我说: “做人做事要兢兢业业、勤勤恳恳、本本分分,人要是行,干一行行一行。一行行行行行;要是不行,干一行不行一行,一行不行行行不行。” 我现在整个人红红火火恍恍惚惚的……
请听题:请实现大锤的自动校对程序
输入描述
第一行包括一个数字N,表示本次用例包括多少个待校验的字符串。
后面跟随N行,每行为一个待校验的字符串。
输出描述
N行,每行包括一个被修复后的字符串。
输入例子
2
helloo
wooooooow
输出例子
hello
woow
思路
用一个指针j
j记录删除字符后的指向字符串的位置,删除时用后面的字符覆盖被删除字符即可。
代码
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
using namespace std;
void verify(string& s) {
int j = 0;
for(int i = 0; i < s.size(); ++i) {
s[j++] = s[i];
if(j >= 3 && s[j - 1] == s[j - 2] && s[j - 2] == s[j - 3])
--j;
if(j >= 4 && s[j - 1] == s[j - 2] && s[j - 3] == s[j - 4])
--j;
}
s.erase(s.begin() + j, s.end()); // 删除多余空间
}
void verify(vector<string>& strs) {
for (string& s : strs) {
verify(s);
}
}
int main() {
int n;
while (cin >> n) {
vector<string> strs(n);
int i = 0;
while (i < n && cin >> strs[i++]);
verify(strs);
for (string& s : strs) {
cout << s << endl;
}
}
return 0;
}
题目二
我叫王大锤,是一名特工。我刚刚接到任务:在字节跳动大街进行埋伏,抓捕恐怖分子孔连顺。和我一起行动的还有另外两名特工,我提议
- 我们在字节跳动大街的N个建筑中选定3个埋伏地点。
- 为了相互照应,我们决定相距最远的两名特工间的距离不超过D。
我特喵是个天才! 经过精密的计算,我们从X种可行的埋伏方案中选择了一种。这个方案万无一失,颤抖吧,孔连顺!
……
万万没想到,计划还是失败了,孔连顺化妆成小龙女,混在cosplay的队伍中逃出了字节跳动大街。只怪他的伪装太成功了,就是杨过本人来了也发现不了的!
请听题:给定N(可选作为埋伏点的建筑物数)、D(相距最远的两名特工间的距离的最大值)以及可选建筑的坐标,计算在这次行动中,大锤的小队有多少种埋伏选择。
注意:
- 两个特工不能埋伏在同一地点
- 三个特工是等价的:即同样的位置组合(A, B, C) 只算一种埋伏方法,不能因“特工之间互换位置”而重复使用
输入描述
第一行包含空格分隔的两个数字 N和D(1 ≤ N ≤ 1000000; 1 ≤ D ≤ 1000000)
第二行包含N个建筑物的的位置,每个位置用一个整数(取值区间为[0, 1000000])表示,从小到大排列(将字节跳动大街看做一条数轴)
输出描述
一个数字,表示不同埋伏方案的数量。结果可能溢出,请对 99997867 取模
输入例子1
4 3
1 2 3 4
输出例子1
4
例子说明1
可选方案 (1, 2, 3), (1, 2, 4), (1, 3, 4), (2, 3, 4)
输入例子2
5 19
1 10 20 30 50
输出例子2
1
例子说明2
可选方案 (1, 10, 20)
思路
应使两名特工的距离尽可能远但不能超过D,可用双指针法(滑动窗口),先固定左界,右界移动到第一个大于D的位置,因为已确定左边的特工,故其余两名特工为这段距离的取2组合数。
代码
#include <iostream>
#include <vector>
using namespace std;
const uint64_t MOD = 99997867;
uint64_t getRes(vector<uint64_t>& pos, uint64_t N, uint64_t D) {
uint64_t res = 0;
for (uint64_t i = 0, j = 0; i < N - 2; ++i) {
while (j < N && pos[j] - pos[i] <= D) ++j;
if (j - i - 1 >= 2) {
uint64_t n = j - i - 1;
uint64_t tmp = n * (n - 1) / 2;
res += tmp % MOD;
}
}
return res % MOD;
}
int main() {
uint64_t N, D;
while (cin >> N >> D) {
vector<uint64_t> pos(N);
uint64_t i = 0;
while (i < N && cin >> pos[i++]);
cout << getRes(pos, N, D) << endl;
}
return 0;
}
题目三
小包最近迷上了一款叫做雀魂的麻将游戏,但是这个游戏规则太复杂,小包玩了几个月了还是输多赢少。
于是生气的小包根据游戏简化了一下规则发明了一种新的麻将,只留下一种花色,并且去除了一些特殊和牌方式(例如七对子等),具体的规则如下:
总共有36张牌,每张牌是1~9。每个数字4张牌。
你手里有其中的14张牌,如果这14张牌满足如下条件,即算作和牌
14张牌中有2张相同数字的牌,称为雀头。
除去上述2张牌,剩下12张牌可以组成4个顺子或刻子。顺子的意思是递增的连续3个数字牌(例如234,567等),刻子的意思是相同数字的3个数字牌(例如111,777)
例如:
1 1 1 2 2 2 6 6 6 7 7 7 9 9 可以组成1,2,6,7的4个刻子和9的雀头,可以和牌
1 1 1 1 2 2 3 3 5 6 7 7 8 9 用1做雀头,组123,123,567,789的四个顺子,可以和牌
1 1 1 2 2 2 3 3 3 5 6 7 7 9 无论用1 2 3 7哪个做雀头,都无法组成和牌的条件。
现在,小包从36张牌中抽取了13张牌,他想知道在剩下的23张牌中,再取一张牌,取到哪几种数字牌可以和牌。
输入描述1
输入只有一行,包含13个数字,用空格分隔,每个数字在1~9之间,数据保证同种数字最多出现4次。
输出描述1
输出同样是一行,包含1个或以上的数字。代表他再取到哪些牌可以和牌。若满足条件的有多种牌,请按从小到大的顺序输出。若没有满足条件的牌,请输出一个数字0
输入例子1
1 1 1 2 2 2 5 5 5 6 6 6 9
输出例子1
9
例子说明1
可以组成1,2,6,7的4个刻子和9的雀头
输入例子2
1 1 1 1 2 2 3 3 5 6 7 8 9
输出例子2
4 7
例子说明2
用1做雀头,组123,123,567或456,789的四个顺子
输入例子3
1 1 1 2 2 2 3 3 3 5 7 7 9
输出例子3
0
例子说明3
来任何牌都无法和牌
思路
存在多种可能组合找出所有可能组合或其中一种满足条件的可能都可以用回溯法解决,关键是要找准递归结束条件。这里的递归结束条件是:将所有可能的顺子和刻子全部拿掉后剩下的两张牌能否构成雀头。
代码
#include <iostream>
#include <vector>
using namespace std;
const int total = 10;
const int num = 13;
void isWin(int cards[], int index, bool& res) {
if (index == 12) {
for (int i = 1; i < total; ++i) {
if (cards[i] == 2) {
res = true;
return;
}
}
}
for (int i = 1; i < total; ++i) {
if (cards[i] >= 3) {
cards[i] -= 3;
isWin(cards, index + 3, res);
cards[i] += 3;
}
if (i + 2 < total
&& cards[i] >= 1
&& cards[i + 1] >= 1
&& cards[i + 2] >= 1) {
cards[i] -= 1; cards[i + 1] -= 1; cards[i + 2] -= 1;
isWin(cards, index + 3, res);
cards[i] += 1; cards[i + 1] += 1; cards[i + 2] += 1;
}
}
}
vector<int> getRes(int nums[]) {
vector<int> res;
int cards[total] = { 0 };
for (int i = 0; i < num; ++i)
++cards[nums[i]];
for (int i = 1; i < total; ++i) {
if (cards[i] < 4) {
++cards[i];
bool found = false;
isWin(cards, 0, found);
if (found)
res.push_back(i);
--cards[i];
}
}
return res;
}
int main() {
int nums[num];
int i = 0;
while (i < num && cin >> nums[i++]);
vector<int> res = getRes(nums);
if (res.size() == 0) {
cout << 0 << endl;
}
else {
for (int i : res) {
cout << i << " ";
}
cout << endl;
}
return 0;
}
题目四
小明是一名算法工程师,同时也是一名铲屎官。某天,他突发奇想,想从猫咪的视频里挖掘一些猫咪的运动信息。为了提取运动信息,他需要从视频的每一帧提取“猫咪特征”。一个猫咪特征是一个两维的vector<x, y>。如果x_1=x_2 and y_1=y_2,那么这俩是同一个特征。
因此,如果喵咪特征连续一致,可以认为喵咪在运动。也就是说,如果特征<a, b>在持续帧里出现,那么它将构成特征运动。比如,特征<a, b>在第2/3/4/7/8帧出现,那么该特征将形成两个特征运动2-3-4 和7-8。
现在,给定每一帧的特征,特征的数量可能不一样。小明期望能找到最长的特征运动。
输入描述:
第一行包含一个正整数N,代表测试用例的个数。
每个测试用例的第一行包含一个正整数M,代表视频的帧数。
接下来的M行,每行代表一帧。其中,第一个数字是该帧的特征个数,接下来的数字是在特征的取值;比如样例输入第三行里,2代表该帧有两个猫咪特征,<1,1>和<2,2>
所有用例的输入特征总数和<100000
N满足1≤N≤100000,M满足1≤M≤10000,一帧的特征个数满足 ≤ 10000。
特征取值均为非负整数。
输出描述
对每一个测试用例,输出特征运动的长度作为一行
输入例子
1
8
2 1 1 2 2
2 1 1 1 4
2 1 1 2 2
2 2 2 1 4
0
0
1 1 1
1 1 1
输出例子
3
例子说明
特征<1,1>在连续的帧中连续出现3次,相比其他特征连续出现的次数大,所以输出3
思路
用散列表记录连续出现的项的下标,然后再统计最大连续数。
代码
#include <iostream>
#include <vector>
#include <map>
#include <algorithm>
using namespace std;
class Point {
public:
int x;
int y;
Point() : x(0), y(0) { }
Point(int x_, int y_) : x(x_), y(y_) { }
bool operator==(const Point& pt) const {
return this->x == pt.x && this->y == pt.y;
}
bool operator!=(const Point& pt) const {
return !this->operator==(pt);
}
bool operator<(const Point& pt) const {
return this->x == pt.x ? this->y < pt.y : this->x < pt.x;
}
};
int getMaxCnt(vector<vector<Point>>& frames, int M) {
map<Point, vector<int>> hash;
for (size_t i = 0; i < M; ++i) {
for (size_t j = 0; j < frames[i].size(); ++j) {
if (hash.find(frames[i][j]) == hash.end()) {
hash[frames[i][j]] = vector<int>(1, i);
} else {
hash[frames[i][j]].push_back(i);
}
}
}
int res = 1;
for (auto it = hash.begin(); it != hash.end(); ++it) {
auto record = it->second;
int cnt = 1;
for (size_t i = 1; i < record.size(); ++i) {
if (record[i - 1] + 1 == record[i]) {
++cnt;
} else {
cnt = 1;
}
res = max(res, cnt);
}
}
return res;
}
int main() {
int N, M;
int features;
cin >> N;
for (int i = 0; i < N; ++i) {
cin >> M;
vector<vector<Point>> frames;
for (int j = 0; j < M; ++j) {
cin >> features;
vector<Point> temp(features);
for (int k = 0; k < features; ++k) {
cin >> temp[k].x >> temp[k].y;
}
frames.push_back(temp);
}
cout << getMaxCnt(frames, M) << endl;
}
return 0;
}
题目五
小明目前在做一份毕业旅行的规划。打算从北京出发,分别去若干个城市,然后再回到北京,每个城市之间均乘坐高铁,且每个城市只去一次。由于经费有限,希望能够通过合理的路线安排尽可能的省一些路上的花销。给定一组城市和每对城市之间的火车票的价钱,找到每个城市只访问一次并返回起点的最小车费花销。
输入描述
城市个数n(1<n≤20,包括北京)
城市间的车票价钱 n行n列的矩阵 m[n][n]
输出描述
最小车费花销 s
输入例子
4
0 2 6 5
2 0 4 4
6 4 0 2
5 4 2 0
输出例子
13
例子说明
共 4 个城市,城市 1 和城市 1 的车费为0,城市 1 和城市 2 之间的车费为 2,城市 1 和城市 3 之间的车费为 6,城市 1 和城市 4 之间的车费为 5,依次类推。假设任意两个城市之间均有单程票可购买,且票价在1000元以内,无需考虑极端情况。
思路
标准旅行商问题(Travelling salesman problem, TSP),这里有很好解析。
代码
#include <iostream>
#include <vector>
#include <algorithm>
#include <limits.h>
using namespace std;
int getMinCost(vector<vector<int>>& prices, int n) {
int v = 1 << (n - 1);
vector<vector<int>> dp(n, vector<int>(v));
for (int i = 0; i < n; ++i)
dp[i][0] = prices[i][0];
for (int j = 1; j < v; ++j) {
for (int i = 0; i < n; ++i) {
dp[i][j] = INT_MAX;
if (((j >> (i - 1)) & 1) == 0) {
for (int k = 1; k < n; ++k) {
if (((j >> (k - 1)) & 1) == 1) {
dp[i][j] = min(dp[i][j], prices[i][k] + dp[k][j ^ (1 << (k - 1))]);
}
}
}
}
}
return dp[0][v - 1];
}
int main() {
int n;
cin >> n;
vector<vector<int>> prices(n, vector<int>(n));
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
cin >> prices[i][j];
}
}
cout << getMinCost(prices, n) << endl;
return 0;
}
题目六
Z国的货币系统包含面值1元、4元、16元、64元共计4种硬币,以及面值1024元的纸币。现在小Y使用1024元的纸币购买了一件价值为 N ( 0 < N ≤ 1024 ) N(0<N\leq1024) N(0<N≤1024)的商品,请问最少他会收到多少硬币?
输入描述
一行,包含一个数N。
输出描述
一行,包含一个数,表示最少收到的硬币数。
输入例子
200
输出例子
17
例子说明
花200,需要找零824块,找12个64元硬币,3个16元硬币,2个4元硬币即可。
思路
贪心法,从大到小找;或者是动态规划法,可看作0-1背包问题。
代码
#include <iostream>
using namespace std;
int getMinCoin(int N) {
int remain = 1024 - N;
int coins[4] = { 64, 16, 4, 1 };
int cnt = 0;
for (int c : coins) {
cnt += remain / c;
remain %= c;
}
return cnt;
}
int getMinCoin2(int N) {
int remain = 1024 - N;
vector<int> coins = { 64, 16, 4, 1 };
vector<int> dp(remain + 1, 1024);
dp[0] = 0;
for (int i = 1; i < remain + 1; ++i) {
for (int c : coins) {
if (i - c >= 0) {
dp[i] = min(dp[i - c] + 1, dp[i]);
}
}
}
return dp[remain];
}
int main() {
int N;
cin >> N;
cout << getMinCoin(N) << endl;
return 0;
}
题目七
机器人正在玩一个古老的基于DOS的游戏。游戏中有N+1座建筑——从0到N编号,从左到右排列。编号为0的建筑高度为0个单位,编号为i的建筑的高度为H(i)个单位。
起初, 机器人在编号为0的建筑处。每一步,它跳到下一个(右边)建筑。假设机器人在第k个建筑,且它现在的能量值是E, 下一步它将跳到第个k+1建筑。它将会得到或者失去正比于与H(k+1)与E之差的能量。如果 H(k+1) > E 那么机器人就失去 H(k+1) - E 的能量值,否则它将得到 E - H(k+1) 的能量值。
游戏目标是到达第个N建筑,在这个过程中,能量值不能为负数个单位。现在的问题是机器人以多少能量值开始游戏,才可以保证成功完成游戏?
输入描述
第一行输入,表示一共有 N 组数据.
第二个是 N 个空格分隔的整数,H1, H2, H3, …, Hn 代表建筑物的高度
输出描述
输出一个单独的数表示完成游戏所需的最少单位的初始能量
输入例子1
5
3 4 3 2 4
输出例子1
4
输入例子2
3
4 4 4
输出例子2
4
输入例子3
3
1 6 4
输出例子3
3
思路
可以推导出递推公式
E
k
=
2
E
k
−
1
−
H
k
E_k=2E_{k-1}-H_k
Ek=2Ek−1−Hk
于是反推公式为
E
k
−
1
=
(
E
k
+
H
k
)
/
2
E_{k-1}=(E_k+H_k)/2
Ek−1=(Ek+Hk)/2
最后一步的能量值必定至少为零,于是从右往左递推得到初始至少所需的能量值。
代码
#include <iostream>
#include <vector>
using namespace std;
int getMinEnergy(vector<int>& H, int N) {
int E = 0;
for (int i = N; i >= 1; --i) {
E = (E + H[i] + 1) / 2;
}
return E;
}
int main() {
int N;
cin >> N;
vector<int> H(N + 1);
for (int i = 1; i <= N; ++i) {
cin >> H[i];
}
cout << getMinEnergy(H, N) << endl;
return 0;
}