时间:2020.01.19
个人博客:https://wyjoutstanding.github.io/
第一次参加算法比赛,记录一下
题号 | 考点 | 备注 |
---|---|---|
A | 外心坐标计算 | 手算/线性方程 |
B | 哈诺塔递归设计 | 手动调试 |
C | 前缀和/线性化 | 固定起点(日期计算) |
D | 拓扑排序,通路计数 | 简单dp,bfs |
E | 简单模拟 | 注意四舍五入 |
F | 找规律,大整数 | 手动猜想/通式带入化简 |
G | 质因数分解 | 仅需计算质因数个数 |
H | 直接输出 | 签到题 |
I | 子序列计数dp | 包括连续和非连续,滚动数组优化(逆序遍历) |
J | 三维迷宫 | BFS搜索6个方向 |
难度排序:
已知三点坐标,求解外心坐标(中垂线交点)。
可直接用两点式写出直线方程,联立求解二元一次方程,手算出(x,y)结果。
或者用高斯消元求解线性方程。
#include
using namespace std;
int main() {
double p[3][2], m[2][2], A[2], B[2]; // 三点坐标,中点坐标,中垂线法向量
for (int i = 0; i < 3; i ++) scanf("%lf %lf", &p[i][0], &p[i][1]);
for (int i = 0; i < 2; i ++) {
m[i][0] = (p[i][0] + p[i+1][0]) / 2; // x
m[i][1] = (p[i][1] + p[i+1][1]) / 2; // y
A[i] = p[i][0] - p[i+1][0]; // 法向量x
B[i] = p[i][1] - p[i+1][1]; // 法向量y
}
double x, y;
x = -(-B[1]*A[0]*m[0][0] - B[0]*B[1]*m[0][1] + B[0]*B[1]*m[1][1] + B[0]*A[1]*m[1][0]) / (B[1]*A[0] - B[0]*A[1]);
y = m[0][1] - A[0]*(x-m[0][0])/B[0];
printf("%.3lf %.3lf\n", x, y);
return 0;
}
极有意思的一道题,深入理解了汉诺塔递归抽象原理。
只需在原来的汉诺塔基础上,控制奇偶放置位置即可。
递归设计比较简单,建议手动演示推导。
void hanoi(int n, vector<int>& A, vector<int>& B, vector<int>& C) { // 递归实现:表示将A柱上n个盘子借助B移动到C
if (n == 1) { // 1个直接移动
move(A, C);
return;
}
hanoi(n-1, A, C, B); // A柱上n-1个盘子借助C移动到B
move(A, C);
hanoi(n-1, B, A, C);
}
#include
using namespace std;
int n, cnt = 0, p[3]; // 总个数,移动次数,三根柱子的位置
vector<int> V[3]; // 3个柱子
char a[20][100]; // 存放输出
void showRst() { // 打印结果
int N = 3*(2*n+1) + 4; // 列宽
for (int i = 0; i < n + 3; i ++) { // 初始化
for (int j = 0; j < N; j ++) {
if (i == n + 2) a[i][j] = '-';
else {
if (i != 0 && (p[0] == j || p[1] == j || p[2] == j)) a[i][j] = '|';
else a[i][j] = '.';
}
}
}
for (int i = 0; i < 3; i ++) { // 三个柱子对应的盘子打印
int j = n+1; // 从n+1逆向打印
for (auto k : V[i]) {
for (int k2 = p[i] - k; k2 <= p[i] + k; k2 ++) a[j][k2] = '*'; // 盘子宽度:2k+1
j --;
}
}
++ cnt; // 统计移动次数
for (int i = 0; i < n+3; i ++) { // 打印最终结果
if ((cnt == 1 << n) && i == n + 2) break; // 排除最后一次移动的分割线
for (int j = 0; j < N; j ++) printf("%c", a[i][j]);
printf("\n");
}
}
void move(vector<int>& A, vector<int>& B) { // 将A的最后一个元素移到B的最后一个元素后
B.push_back(A.back());
A.pop_back();
showRst(); // 移动就打印结果
}
void hanoi(int n, vector<int>& A, vector<int>& B, vector<int>& C) { // 递归实现:表示将A柱上n个盘子借助B移动到C
if (n == 1) { // 1个直接移动
move(A, C);
return;
}
hanoi(n-1, A, C, B); // A柱上n-1个盘子借助C移动到B
move(A, C);
hanoi(n-1, B, A, C);
}
int main() {
scanf("%d", &n);
for (int i = n; i > 0; i --) V[0].push_back(i); // 初始化
for (int i = 0; i < 3; i ++) {
p[i] = (2*n+2)*i + 1 + n; // 每个柱子的位置
}
showRst(); // 原始打印
if (n % 2 == 0) hanoi(n, V[0], V[1], V[2]); // 根据奇偶控制转移到的位置
else hanoi(n, V[0], V[2], V[1]);
return 0;
}
类似于时间段求解(前缀和),可固定起点1,分别计算出1(t1-1)和1t2的时间,做差即为结果。
对于时间1~t的求解,除法和求余即可。
#include
using namespace std;
long long getTime(long long t) {
long long mod = t % 60;
return (t / 60) * 50 + ((mod <= 50) ? mod : 50);
}
int main() {
long long t1, t2;
while(scanf("%lld %lld", &t1, &t2) == 2) {
printf("%lld\n", getTime(t2) - getTime(t1-1));
}
return 0;
}
题目表意不清,说得天花乱坠,其实就是给定一个有向无环图,找出结点1~n的路径条数。
这里的最优策略指得是同一时间接受请求越多,则攻击越成功,因为可以通过调整每条通路开始时间,使到达n的所有请求时刻一致。
因此,在拓扑排序过程中即可完成路径数计算,其中dp[i]表示从1到大i的路径数目,对于单向边u->v,当u的入度为0时,dp[v]+=dp[u]
注意取模,且需要考虑重边问题,若用邻接表存储则无需考虑
测试样例:
//输入
5 8
1 2 3
1 3 1
2 5 1
3 5 3
1 2 3
1 4 1
2 4 1
4 5 1
//输出
6
#include
using namespace std;
#define MAXN 100010
#define MOD 20010905
vector<int> adj[MAXN], dp(MAXN, 0), indegree(MAXN, 0); // 邻接表,dp[i]表示达到节点i的路径数,入度
int main() {
int n, m, u, v, w;
scanf("%d %d", &n, &m);
for (int i = 0; i < m; i ++) {
scanf("%d %d %d", &u, &v, &w);
adj[u].push_back(v);
indegree[v] ++; // 入度计算
}
// 拓扑排序过程计算条数
queue<int> q;
for (int i = 1; i <= n; i ++) { // 将第一批入度为0顶点入队
if (indegree[i] == 0) {
dp[i] = 1;
q.push(i);
}
}
while(!q.empty()) {
u = q.front(); q.pop();
for (auto i : adj[u]) {
dp[i] = (dp[i] + dp[u]) % MOD; // 计算转移到下一点的路径数
indegree[i] --; // 入度扣除
if (indegree[i] == 0) q.push(i); // 入度为0则入队
}
}
printf("%d\n", dp[n]);
return 0;
}
根据题目规定计算分数即可,注意以下几点:
课程性质为任选(编号2)的不计算
每科平时分,期中,期末分数乘以相应比例再求和后,需要四舍五入取整
最终结果需四舍五入(保留2位小数,即求解到千分位)
#include
using namespace std;
int main() {
double credit, tot=0.0, s, p, sum1=0.0, sum2=0.0;
int n, t;
scanf("%d", &n);
while(n --) {
tot = 0.0;
scanf("%d %lf", &t, &credit);
for (int i = 0; i < 3; i ++) {
scanf("%lf %lf", &s, &p);
tot += s * p;
}
if (t != 2) { // 忽略任选科目
sum1 += credit; // 学分累加
sum2 += (int)(tot+0.5) * credit; // 四舍五入
}
}
// 处理保留两位小数,根据千分位进行四舍五入
double ans = sum2 / sum1;
ans = (int)(ans*100+0.5) / 100.0;
printf("%.2lf\n", ans);
return 0;
}
斐波那契数列翻新,两种思路,容易发现fn=(-1)^n
:
对于输入值,乍一看需大整数处理,实则不用,只需用字符串读入,判断最后一位的奇偶性即可。
#include
using namespace std;
int main() {
string s;
cin >>s;
printf("%d\n", ((s.back() - '0') % 2 == 0) ? 1 : -1);
return 0;
}
可操作次数,就是n的质因数个数-1。比如8=2*2*2,3个质因数,可进行两次分解操作,再如40=2*2*2*5,4个质因数,3次分解操作。
除此之外,注意n=1时为特例,0次分解操作。
#include
using namespace std;
#define N 400
int main() {
int n, cnt = 0;
scanf("%d", &n);
for (int i = 2; i < N && n != 1; i ++) { // 统计能分解的质因数个数
while(n % i == 0 && n != i) { // 不包含最后一个自身
cnt += 1; // 累计次数
n /= i; // 下一个数
}
}
printf("%s\n", ((cnt % 2) == 0) ? "Nancy" : "Johnson");
return 0;
}
签到题,输出题目即可
#include
using namespace std;
int main() {
printf("\"Happy New Year!\"");
return 0;
}
子序列计数,简单dp加滚动数组优化。
dp[i]表示在当前长度下,满足S[1…i]序列的方案数,根据下一个字符ch及ch的前一个字符计算个数。
若用滚动数组优化,最好逆序求解,避免存在相同的字母照成感染,如aa这种情况(不过这题不存在这种情况)
#include
using namespace std;
#define MOD 20010905
int dp[9] = {1,0}; // dp[0]=1,为第一个字符做准备
int main() {
string s, str = "tiloveyou";
cin >> s;
for (auto ch : s) {
for (int i = 8; i >= 1; i --) { // 逆序遍历,避免相邻相同的字符相互感染
dp[i] = (dp[i] + (str[i] == tolower(ch)) * dp[i-1]) % MOD; // DP滚动数组优化
}
}
printf("%d\n", dp[8]);
return 0;
}
二维迷宫进阶,三维迷宫用bfs求解。
只需在二维迷宫遍历前后左右四个方向基础上,增加上下两个方向即可。
#include
using namespace std;
#define N 101;
int maze[101][101][101] = {0}; // 存储迷宫地图,1表示不通
int dict[6][3] = {{0,0,1}, {0,0,-1}, {0,1,0}, {1,0,0}, {0,-1,0}, {-1,0,0}}; // 控制前后左右上下六个方向(x,y,z)
struct Pos {
int x, y, z, d; // x,y,z坐标和层次
Pos(int _x, int _y, int _z, int _d): x(_x), y(_y), z(_z), d(_d){}
Pos(){}
}pos;
int n, x, y, z, ans = -1;
int main() {
char t;
scanf("%d", &n);
for (int i = 0; i < n; i ++) {
for (int j = 0; j < n; j ++) {
for (int k = 0; k < n; k ++) {
scanf(" %c", &t); // 空格用来吸收空格和换行
if (t != '.') maze[i][j][k] = 1; // 不可走
}
}
}
queue<Pos> q;
q.push(Pos(0,0,0,1));
while(!q.empty()) { // BFS
pos = q.front();
q.pop();
if (pos.x == n - 1 && pos.y == n-1 && pos.z == n-1) { // 终点
ans = pos.d;
break;
}
for (int i = 0; i < 6; i ++) { // 6个方向
x = pos.x + dict[i][0]; y = pos.y + dict[i][1]; z = pos.z + dict[i][2];
if (x >= 0 && x < n && y >= 0 && y < n && z >= 0 && z < n // 范围合法
&& maze[x][y][z] == 0) { // 未访问
maze[x][y][z] = 1; // 置为已访问
q.push(Pos(x,y,z,pos.d+1));
}
}
}
printf("%d\n", ans);
return 0;
}
说是小白月赛,第一次参加算法比赛,只做出4道题,汗颜,因此,用了两天时间将不会的题目补了出来,其实真不难,只是第一次遇见不知道咋做。
这个题目出得不太严谨,测试用例规模描述太少,题目名称为了和序号一致显得牵强附会
不过通过比赛这种形式可以提高学习效率,查看他人AC代码学习到了很多。
写算法确实对许多问题有了更深刻理解,比如汉诺塔,大一C语言就做过,这里重新来了一遍,整个过程明了不少,同时还学会了如何求解线性方程组与一些基础的数论知识。