去年参加了一次天梯赛,毕竟才大一,水平有限,L1基本答了,L2混了几题部分正确,L3题目还没点开过。今年再战,顺便更新题解,也作自己复习。另外,平常HDU题做多了,养成了动不动就用scanf
和printf
的习惯,其实PTA的题基本上用cin
和cout
都能过的,所以 下面的代码中的所有c语言部分为了方便都可以换成c++,毕竟scanf,printf不仅字母多,里面还要打一堆%
太繁琐了。
L1部分题解
由于L1题目普遍简单,所以备注比较少,后面的L2题目会适当加上备注。
L1-001
最难的一题!!!
#include
using namespace std;
int main() {
cout << "Hello World!";
return 0;
}
L1-002
因为图形上下对称,所以可以先计算出上半图形的高度、宽度,这题主要就是考察逻辑思维。
#include
using namespace std;
int main() {
int n, height = 1, width = 1;
char ch;
cin >> n >> ch;
--n; // 先扣去最中间的一个字符
while (true) {
if (n - 2 * (width + 2) >= 0) {
++height;
width += 2;
n -= 2 * width;
} else
break;
}
for (int i = 0; i < height; ++i) {
for (int j = 0; j < i; ++j) cout << ' ';
for (int j = 0; j < 2 * height - 1 - 2 * i; ++j) cout << ch;
cout << '\n';
}
for (int i = 0; i < height - 1; ++i) {
for (int j = 0; j < height - 2 - i; ++j) cout << ' ';
for (int j = 0; j < 2 * (i + 1) + 1; ++j) cout << ch;
cout << '\n';
}
cout << n;
return 0;
}
L1-003
虽然题目描述需要输入正整数,但是输入正整数需要一次一次取模才能得到各位,直接以字符串的方式输入就很简单。
#include
using namespace std;
int main() {
int cnt[10] = {0};
string num;
cin >> num;
for (int i = 0; i < num.size(); ++i) ++cnt[num[i] - '0'];
for (int i = 0; i < 10; ++i)
if (cnt[i] > 0) cout << i << ':' << cnt[i] << '\n';
return 0;
}
L1-004
没有什么难点,L1很多题都像这样。
#include
using namespace std;
int main() {
int F;
cin >> F;
int C = 5 * (F - 32) / 9;
cout << "Celsius = " << C;
return 0;
}
L1-005
把试机座位号当作下标,然后准考证号和考试座位号分别存入a, b数组,最后输出就好。(准考证号16位,int装不下)
#include
using namespace std;
typedef long long LL;
LL x, a[1005];
int n, m, y, z, b[1005];
int main() {
scanf("%d", &n);
for (int i = 0; i < n; ++i) {
scanf("%lld%d%d", &x, &y, &z);
a[y] = x;
b[y] = z;
}
scanf("%d", &m);
for (int i = 0; i < m; ++i) {
scanf("%d", &y);
printf("%lld %d\n", a[y], b[y]);
}
return 0;
}
L1-006
用循环模拟出可能的连续因子序列。需要注意几个优化的地方,for (int i = 2; i <= sqrt(n); ++i)
,很好理解,大于sqrt(n)
的数再随便乘一下就大于n了,肯定就不是因子,所以这里只走到sqrt(n)
。
#include
using namespace std;
typedef long long LL;
LL n, tmp;
int start, len;
int main() {
scanf("%lld", &n);
for (int i = 2; i <= sqrt(n); ++i) {
tmp = 1;
for (int j = i; tmp * j <= n; ++j) {
tmp *= j;
if (n % tmp) break; // 如果tmp不是n的因子,再乘任何数也不是
if (j - i + 1 > len) {
start = i;
len = j - i + 1;
}
}
}
if (!start) {
/** 最后一种质数的情况,质数只能被1和本身整除
* 这是这题最坑的一个地方,百度了因子的定义
* 因子并不包括数本身,因数才包括自己本身,例如10
* 因子就是1,2,5 因数就是1,2,5,10
* 可这题偏偏就要输出这个本身。。。
*/
start = n;
len = 1;
}
printf("%d\n%d", len, start);
for (int i = 1; i < len; ++i) printf("*%d", start + i);
return 0;
}
L1-007
只要注意行尾不能有空格就好了。
#include
using namespace std;
string a[] = {"ling", "yi", "er", "san", "si", "wu", "liu", "qi", "ba", "jiu"};
int main() {
string s;
cin >> s;
if (s[0] == '-')
cout << "fu";
else
cout << a[s[0] - '0'];
for (int i = 1; i < s.size(); ++i)
cout << ' ' << a[s[i] - '0'];
return 0;
}
L1-008
只能说pta的题太细节了,不难的一题但是正确率很低,因为很容易出现刚好5个数时,最后连换两行的情况。
#include
using namespace std;
int main() {
int a, b, sum = 0;
scanf("%d%d", &a, &b);
for (int i = a; i <= b; ++i) {
sum += i;
printf("%5d", i);
if ((i - a + 1) % 5 == 0)
printf("\n");
else if (i == b)
printf("\n");
}
printf("Sum = %d", sum);
return 0;
}
L1-059
就是几个知识点,getline
前用 cin.get()
吸收回车,然后一些字符串基本操作,find
正向查找,rfind
反向查找,substr
切割。最后就是需要考虑到一种特殊情况,逗号前的字符不足3个,那么直接去使用 str.substr(p - 3, 3) == "ong"
判断的话就会出现错误(最后一组测试数据),所以需要加上 p >= 3
这个前提条件。
#include
using namespace std;
int main() {
string str;
int n;
cin >> n;
cin.get();
while (n--) {
getline(cin, str);
int p = str.find(',');
if (p >= 3 && str.substr(p - 3, 3) == "ong" &&
str.substr(str.size() - 4, 3) == "ong") {
p = str.size();
for (int i = 0; i < 3; ++i) p = str.rfind(' ', p - 1);
cout << str.substr(0, p) << " qiao ben zhong.\n";
} else
cout << "Skipped\n";
}
return 0;
}
L2部分题解
L2-001 (dijkstra)
复杂了一些的最短路,用优先队列对查找最小dis
的过程做了优化。dis[i]
表示起点到i
的最短路长度,pre[i]
表示i
的前驱,cnt[i]
表示到达i
的最短路条数,sum[i]
表示最多的救援队数目。
#include
#define INF 0x3f3f3f3f
#define N 505
using namespace std;
int n, m, s, d, x, y, z;
int num[N], path[N][N], dis[N], pre[N], cnt[N], sum[N];
bool vis[N];
struct Node {
int key, value;
Node(int k, int v) {
key = k;
value = v;
}
bool operator<(const Node &a) const { return value > a.value; }
};
void dijkstra() {
pre[s] = -1;
cnt[s] = 1;
sum[s] = num[s];
dis[s] = 0;
priority_queue q;
q.push(Node(s, 0));
while (!q.empty()) {
int k = q.top().key;
q.pop();
if (vis[k]) continue;
vis[k] = true;
for (int i = 0; i < n; ++i) {
if (!vis[i]) {
int cur_dis = dis[k] + path[k][i];
int cur_sum = sum[k] + num[i];
if (cur_dis < dis[i]) {
dis[i] = cur_dis;
pre[i] = k;
cnt[i] = cnt[k];
sum[i] = cur_sum;
} else if (cur_dis == dis[i]) {
cnt[i] += cnt[k];
if (cur_sum > sum[i]) {
sum[i] = cur_sum;
pre[i] = k;
}
}
q.push(Node(i, dis[i]));
}
}
}
printf("%d %d\n", cnt[d], sum[d]);
stack stk;
int k = d;
while (k != -1) {
stk.push(k);
k = pre[k];
}
printf("%d", stk.top());
stk.pop();
while (!stk.empty()) {
printf(" %d", stk.top());
stk.pop();
}
printf("\n");
}
int main() {
memset(path, INF, sizeof(path));
memset(dis, INF, sizeof(dis));
scanf("%d%d%d%d", &n, &m, &s, &d);
for (int i = 0; i < n; ++i) scanf("%d", &num[i]);
for (int i = 0; i < m; ++i) {
scanf("%d%d%d", &x, &y, &z);
path[x][y] = path[y][x] = z;
}
dijkstra();
return 0;
}
L2-004 (搜索树)
本以为上完数据结构的课应该就会各种树了,结果发现自己还是太年轻。这题按照二叉搜索树的性质建树,先试试是不是二叉搜索树,再试试是不是二叉搜索树的“镜像”,如果最后能建成就顺便输出后序遍历。
#include
using namespace std;
int n, pre[1005], post[1005], cnt = 0;
bool flag;
void build(int l, int r) {
if (l > r) return;
int i = l + 1, j = r;
if (!flag) {
while (i <= r && pre[i] < pre[l]) ++i;
while (l < j && pre[j] >= pre[l]) --j;
} else {
while (i <= r && pre[i] >= pre[l]) ++i;
while (l < j && pre[j] < pre[l]) --j;
}
if (i - j != 1) return;
build(l + 1, j);
build(i, r);
post[cnt++] = pre[l];
}
int main() {
scanf("%d", &n);
for (int i = 0; i < n; ++i) scanf("%d", &pre[i]);
build(0, n - 1);
if (cnt != n) {
flag = true;
cnt = 0;
build(0, n - 1);
}
if (cnt != n)
printf("NO\n");
else {
printf("YES\n");
printf("%d", post[0]);
for (int i = 1; i < cnt; ++i) printf(" %d", post[i]);
printf("\n");
}
return 0;
}
L2-008 (Manacher)
马拉车算法模板题,c(center)表示中间点,r(right)表示回文串的右边界。具体算法思路不说了,还有需要理解的就是r关于c的对称点为2 * c - i
。
#include
using namespace std;
int manacher(string s) {
string str = "$#";
for (int i = 0; i < s.size(); ++i) {
str.push_back(s[i]);
str.push_back('#');
}
int c = 0, r = 0;
vector p(str.size());
for (int i = 1; i < p.size(); ++i) {
p[i] = r > i ? min(p[2 * c - i], r - i + 1) : 1;
while (str[i - p[i]] == str[i + p[i]]) ++p[i];
if (r - c + 1 < p[i]) {
c = i;
r = i + p[i] - 1;
}
}
// return s.substr((2 * c - r) / 2, r - c);
return r - c;
}
int main() {
string s;
getline(cin, s);
cout << manacher(s);
return 0;
}
L2-009 (结构体排序)
结构体排序,输入的钱是分为单位,最后输出时除以100.0得到元。用结构体储存每个人的编号,收入,抢到红包数。
#include
#define N 10005
using namespace std;
struct Node {
int key, sum, cnt;
Node() : sum(0), cnt(0) {}
} a[N];
int n, k, x, y;
bool cmp(Node a, Node b) {
if (a.sum == b.sum) {
if (a.cnt == b.cnt)
return a.key < b.key;
return a.cnt > b.cnt;
}
return a.sum > b.sum;
}
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; ++i) {
a[i].key = i;
scanf("%d", &k);
while (k--) {
scanf("%d%d", &x, &y);
a[i].sum -= y;
a[x].sum += y;
++a[x].cnt;
}
}
sort(a + 1, a + n + 1, cmp);
for (int i = 1; i <= n; ++i)
printf("%d %.2f\n", a[i].key, a[i].sum / 100.0);
return 0;
}
L2-010 (并查集)
并查集,如果两个人是朋友关系就合并,否则,用flag
数组标记两人是敌人。最后直接判断一下输出就好。
#include
#define N 105
using namespace std;
int n, m, k, x, y, z;
int f[N], _rank[N] = {0}, flag[N][N] = {false};
int _find(int x) {
return x == f[x] ? x : f[x] = _find(f[x]); // 路径压缩
}
void _union(int x, int y) {
int f1 = _find(x), f2 = _find(y);
if (f1 != f2) {
if (_rank[f1] < _rank[f2]) // 按秩合并
f[f1] = f2;
else {
f[f2] = f1;
if (_rank[f1] == _rank[f2])
++_rank[f1];
}
}
}
int main() {
scanf("%d%d%d", &n, &m, &k);
for (int i = 1; i <= n; ++i)
f[i] = i;
while (m--) {
scanf("%d%d%d", &x, &y, &z);
if (z == 1)
_union(x, y);
else
flag[x][y] = flag[y][x] = true;
}
while (k--) {
scanf("%d%d", &x, &y);
if (_find(x) == _find(y)) {
if (!flag[x][y])
printf("No problem\n");
else
printf("OK but...\n");
} else {
if (!flag[x][y])
printf("OK\n");
else
printf("No way\n");
}
}
return 0;
}
L2-012 (堆)
肝了我一晚,怎么提交都是部分正确,我是真的无奈,直到看到有个题解说,“用线性调整heap的话是不对的,题目要求是一个一个插入一个空的MinHeap,所以只能插入然后上浮”。我。。。
┏┓ ┏┓
┏┛┻━━━┛┻┓
┃ ┃
┃ ━ ┃
┃ ┳┛ ┗┳ ┃
┃ ┃
┃ ┻ ┃
┃ ┃
┗━┓ ┏━┛
┃ ┃
┃ ┃
┃ ┗━━━┓
┃ ┣┓
┃ ┏┛
┗┓┓┏━┳┓┏┛
┃┫┫ ┃┫┫
┗┻┛ ┗┻┛
思路很粗暴,先建堆(思路就是上面那句话,采用上浮的方式,加一个数浮一次),然后stringstream把单词分割出来装进vector,剩下就是简单判断。
#include
#define N 1005
using namespace std;
int n, m, a[N];
string s;
stringstream ss;
vector v;
void swim(int i) {
int parent = (i - 1) / 2;
if (parent >= 0 && a[parent] > a[i]) {
swap(a[i], a[parent]);
swim(parent);
}
}
int toInt(string &s) {
stringstream ss;
ss << s;
int x;
ss >> x;
return x;
}
int main() {
ios::sync_with_stdio(false);
cin >> n >> m;
for (int i = 0; i < n; ++i) {
cin >> a[i];
swim(i);
}
cin.get();
for (int i = 0; i < m; ++i) {
getline(cin, s);
ss << s;
while (ss >> s) v.push_back(s);
if (v[3] == "root") {
if (toInt(v[0]) == a[0])
cout << "T\n";
else
cout << "F\n";
} else if (v[4] == "siblings") {
int p1 = find(a, a + n, toInt(v[0])) - a;
int p2 = find(a, a + n, toInt(v[2])) - a;
if (((p1 - 1) >> 1) == ((p2 - 1) >> 1))
cout << "T\n";
else
cout << "F\n";
} else if (v[3] == "parent") {
int p1 = find(a, a + n, toInt(v[0])) - a;
int p2 = find(a, a + n, toInt(v.back())) - a;
if ((p2 - 1) >> 1 == p1)
cout << "T\n";
else
cout << "F\n";
} else {
int p1 = find(a, a + n, toInt(v[0])) - a;
int p2 = find(a, a + n, toInt(v.back())) - a;
if ((p1 - 1) >> 1 == p2)
cout << "T\n";
else
cout << "F\n";
}
v.clear();
ss.clear();
}
return 0;
}