比赛题目共2套,其中初赛题1套,复赛2题。
比赛时间: 10:50 - 12:00 a.m。
Part 0x01 过程-Process
\(8:40\,a.m.\) 做初赛题目;
\(10:40\,a.m.\) 拿到题目;
\(10:51\,a.m.\) 先写 \(\text{T2}\),发现是初赛考过的题目,但时限变小;
\(11:20\,a.m.\) 在 \(\text{T2}\) 上花了太久,没调出来,赶紧写 \(\text{T1}\);
\(11:35\,a.m.\) 终于把 \(\text{T1}\) 写完了,估计能过。
\(11:36\,a.m.\) 接着看 \(\text{T2}\),突然发现理解错题目了,但是样例 \(2\) 没过……
\(12:00\,a.m.\) \(\text{T2}\) 发现了一些问题,但是样例 \(2\) 还是没过。
总分 \(= 100pts + 30pts = 130pts\)。
Part 0x02 初赛注意事项-Theory
- \(∨\) 代表或运算,即
||
- \(∧\) 代表与运算,即
&&
- \(?\) 代表非运算,即
!
- 文件型病毒传染的主要对象是
EXE
和COM
文件 - Linux下可执行文件没有后缀名
- 田忌赛马思想:
- 对手必胜,就用最小的马输给对手
- 对手必败,就用最小的马赢对手
- 我方必胜,就用最小的马赢对手
- 我方必败,就用最小的马输给对手
Part 0x03 复赛题目-Problem
T1 幸运质数
正经做法
使用埃式筛筛出 \([1, m]\) 内的所有质数,就可以快速判断了。
My Code:
// g++ "prime.cpp" -Wall -std=c++14 -o "prime"
// ./"prime"
#include <bits/stdc++.h>
using namespace std;
int vis[10000001];
void GetPrime(int n)
{
memset(vis, 0, sizeof(vis));
vis[1] = 1;
vis[2] = 0;
for (int i = 2; i <= n; i++)
{
if(vis[i] == false)
{
for (int j = i * i; j <= n; j += i)
{
vis[j] = 1;
}
}
if(i * i > n)
{
break;
}
}
}
bool Check(int num)
{
while(num > 0)
{
if(vis[num] == true)
{
return false;
}
num /= 10;
}
return true;
}
int main()
{
freopen("prime.in", "r", stdin);
freopen("prime.out", "w", stdout);
int n, m;
scanf("%d %d", &n, &m);
GetPrime(m);
for(int i = n; i <= m; i++)
{
// printf("%d:%d\n", i, vis[i]);
if(Check(i))
{
printf("%d\n", i);
}
}
return 0;
}
时间复杂度 \(O(n\,log_2\,n)\)
打表
注意到 \([1, 10^7]\) 内只有 \(78\) 个幸运素数,直接打表即可。
打表器 (gen.cpp
, 暴力即可):
// g++ "gen.cpp" -Wall -std=c++14 -o "gen"
// ./"gen"
#include <bits/stdc++.h>
using namespace std;
bool IsPrime(int n)
{
if(n == 1)
{
return false;
}
for(int i = 2; i <= sqrt(n); i++)
{
if(n % i == 0)
{
return false;
}
}
return true;
}
bool Check(int num)
{
while(num > 0)
{
if(IsPrime(num) != true)
{
return false;
}
num /= 10;
}
return true;
}
int main()
{
freopen("biao.txt", "w", stdout);
for(int i = 1; i <= 1e7; i++)
{
// printf("%d:%d\n", i, vis[i]);
if(Check(i))
{
printf("%d, ", i);
}
}
return 0;
}
*/
提交代码 (prime.cpp
, 按表输出):
// g++ "prime.cpp" -Wall -std=c++14 -o "prime"
// ./"prime"
#include <bits/stdc++.h>
using namespace std;
const int biao[100] = {0, 2, 3, 5, 7, 23, 29, 31, 37, 53, 59, 71, 73, 79, 233, 239, 293, 311, 313, 317, 373, 379, 593, 599, 719, 733, 739, 797, 2333, 2339, 2393, 2399, 2939, 3119, 3137, 3733, 3739, 3793, 3797, 5939, 7193, 7331, 7333, 7393, 23333, 23339, 23399, 23993, 29399, 31193, 31379, 37337, 37339, 37397, 59393, 59399, 71933, 73331, 73939, 233993, 239933, 293999, 373379, 373393, 593933, 593993, 719333, 739391, 739393, 739397, 739399, 2339933, 2399333, 2939999, 3733799, 5939333, 7393913, 7393931, 7393933};
int main()
{
freopen("prime.in", "r", stdin);
freopen("prime.out", "w", stdout);
int n, m;
scanf("%d %d", &n, &m);
for(int i = 1; i <= 78; i++)
{
if(biao[i] >= n && biao[i] <= m)
{
printf("%d\n", biao[i]);
}
}
return 0;
}
T2 学习
初赛考过这题的暴力解法;
使邻接表存储有向图,使用拓扑排序,注意不能用普通队列,应用优先队列对门槛经验值排序。
My Code:
// g++ "study.cpp" -Wall -std=c++14 -o "study"
// ./"study"
#include <bits/stdc++.h>
using namespace std;
vector <int> G[100010];
int in[100010];
int a[100010], b[100010];
int n, m, w;
struct Node
{
int id;
int need;
};
struct Greater
{
bool operator ()(Node a, Node b)
{
return a.need > b.need;
}
};
void Kahn()
{
priority_queue <Node, vector<Node>, Greater> S;
// queue <int> S;
vector <int> L;
for(int i = 1; i <= n; i++)
{
if(in[i] == 0)
{
S.push((Node){i, a[i]});
}
}
int ans = 0;
while(S.empty() == false)
{
int u = S.top().id;
S.pop();
L.push_back(u);
if (a[u] > w)
{
break;
}
ans++;
w = w + b[u];
for(int v : G[u])
{
in[v]--;
if(in[v] == 0)
{
S.push((Node){v, a[v]});
}
}
}
printf("%d\n", ans);
}
int main()
{
freopen("study.in", "r", stdin);
freopen("study.out", "w", stdout);
scanf("%d %d %d", &n, &m, &w);
for(int i = 1; i <= n; i++)
{
scanf("%d", &a[i]);
}
for(int i = 1; i <= n; i++)
{
scanf("%d", &b[i]);
}
for(int i = 1; i <= m; i++)
{
int u, v;
scanf("%d %d", &u, &v);
G[u].push_back(v);
in[v]++;
}
Kahn();
return 0;
}
时间复杂度 \(O(n+m)\)
Other
题目描述
给出 \(n\) 根长度各异的木棍,求这些木棍能否头尾相连形成一个正方形?
\(1 \leq n \leq 100\)
题解
初赛考了这题的暴力,老师说会考写一下。
类似背包的思路, 设 \(dp_{i, j, k, l}\) 为考虑第 \(i\) 根木棍,当前第一条边长度为 \(j\),第二条边长度为 \(k\),第三条边长度为 \(l\) 是否可行
明显有转移式:
\[dp_{i, j, k, l} = dp_{i-1, j, k, l}\,|\,dp_{i-1, j-a_i, k, l}\,|\,dp_{i-1, j, k-a_i, l}\,|\,dp_{i-1, j, k, l-a_i} \]// g++ "sticks.cpp" -Wall -std=c++14 -o "sticks"
// ./"sticks"
#include <bits/stdc++.h>
using namespace std;
int side;
int n;
int sticks[110];
bool dp[110][110][110][110];
void DP()
{
memset(dp, 0, sizeof(dp));
for(int i = 1; i <= n; i++)
{
dp[1][sticks[i]][0][0] = true;
dp[1][0][sticks[i]][0] = true;
dp[1][0][0][sticks[i]] = true;
}
for(int i = 2; i <= n; i++)
{
for(int j = side; j >= 0; j--)
{
for(int k = side; k >= 0; k--)
{
for(int l = side; l >= 0; l--)
{
dp[i][j][k][l] = dp[i-1][j][k][l];
if(j >= sticks[i])
{
dp[i][j][k][l] |= dp[i-1][j - sticks[i]][k][l];
}
if(k >= sticks[i])
{
dp[i][j][k][l] |= dp[i-1][j][k - sticks[i]][l];
}
if(l >= sticks[i])
{
dp[i][j][k][l] |= dp[i-1][j][k][l - sticks[i]];
}
}
}
}
}
}
int main()
{
int T;
scanf("%d", &T);
while (T--)
{
int length = 0;
scanf("%d", &n);
for(int i = 1; i <= n; i++)
{
scanf ("%d", &sticks[i]);
length += sticks[i];
}
if(length % 4 != 0)
{
printf("no\n");
continue;
}
side = length / 4;
sort(sticks + 1, sticks + n + 1, greater<int>());
if(sticks[1] > side)
{
printf("no\n");
continue;
}
DP();
if(dp[n][side][side][side] == true)
{
printf("yes\n");
}
else
{
printf("no\n");
}
}
return 0;
}
时间复杂度 \(O(n^4)\)
Part 0x04 总结-Summary
- 不要死磕,不要死磕,不要死磕(重要的事情说三遍)
- 注意代码细节
- 存图应使用邻接表
- 当数据小的时候,可以使用
打表
解决问题