部分题目有所难度,特别是最后一题,要得到满分 25 ,还是很难的
比赛:
http://oj.hzjingma.com/contest/status?id=73
试题A:好运2020(暴力枚举)
根据题目,枚举任何一个数的质因子个数。我们可以先将范围内的质数求出,然后对于某一个数,求其质因子个数时,枚举每一个质数(比这个数小的范围内),看看是不是它的因子即可。
答案是:1937
#include <bits/stdc++.h>
using namespace std;
int p[4000];
void init() // 求出范围内的所有质数
{
memset(p, 0, sizeof(p));
for(int i = 2;i < 3000; ++i)
{
if(p[i] == 0)
{
for(int j = 2 * i; j < 3000; j += i)
p[j] = 1;
}
}
}
bool check(int num) // 对某一个数,求其质因子个数
{
int cnt = 0;
for(int i = 2; i <= num; ++i)
{
if(p[i] == 0) // 枚举每一个质数,判断是不是这个数的因子
{
if(num % i == 0) ++cnt;
}
}
if(cnt != 4) return true;
else return false;
}
int main()
{
init();
int ans = 1; // 由于范围是 [1, 2020],但是对于 1,质因子个数为 0,质数是从 2 开始的,枚举范围内每一个数,从 2 开始,那么数字 1 ,质因子个数是0,所以相当于答案 + 1.
for(int i = 2;i <= 2020; ++i)
{
if(check(i))
{
++ans;
}
}
cout << ans << endl;
return 0;
}
试题B:信息加密(简单题)
明文" ABCDEFGHIJKLMNOPQRSTUVWXYZ"
其密文 是"JKLMNOPQRSTUVWXYZABCDEFGHI"。之间的关系就是,对于明文而言,对应的密文就是 : 字母 + 9 。那么对于从密文到明文,如果密文是 c,那么明文就是: c - 'A' - 9 + ’A' (如果 c - 'A' - 9 < 0 ,那就加上 26 即可)
#include <bits/stdc++.h>
using namespace std;
int main()
{
string s;
cin >> s;
int n = s.size();
int d = 'J' - 'A'; // 从明文到密文,是 + d,那么从密文到明文,就是 - d
for(auto& c : s)
{
c = ((c - 'A') - d + 26) % 26 + 'A';
}
cout << s << endl;
return 0;
}
试题C:方圆(判断位置)
要求,矩形和圆的位置关系,其实可以转换为,求圆和矩形的每一条边的关系。
圆与边的关系,就是,求圆心到边的距离(由于这道题的矩形四条边都是平行于坐标轴的,所以很好求)与 圆 半径的关系。
但是还要注意的一点,边的有范围的,当我们判断,圆与边是相交或者相切时,还要多一个条件,即圆心位置,在对应边的范围内,不然可能出现以下情况:
虽然这个会判定为圆与边相切,但是对于圆和矩形的关系,其实相离的
因此对于圆与边相切或者相交的时候,还要加上一个限制条件,即圆心的坐标(x 或 y)要在边的范围中才行,这样子对于圆与矩形的关系才是相切或相交。
#include <bits/stdc++.h>
using namespace std;
bool check(int d, int r) // 判断圆与边的关系
{
if(d <= r)
{
return true;
}
return false;
}
int main()
{
int x0, y0, r, px, py, w, h;
cin >> x0 >> y0 >> r >> px >> py >> w >> h;
if(check(abs(y0 - py),r) && (x0 >= px && x0 <= px + w)) // 当圆与矩形下面那条边判断是相切或相交时,还要求,其 x0 范围,在下面边的 x 范围中(下面的类似)
puts("yes");
else if(check(abs(y0 - py - h),r) && (x0 >= px && x0 <= px + w))
puts("yes");
else if(check(abs(x0 - px),r) && (y0 >= py && y0 <= py + h))
puts("yes");
else if(check(abs(x0 - px - w),r) && (y0 >= py && y0 <= py + h))
puts("yes");
else
puts("no");
return 0;
}
试题D:数据压缩(字符串模拟)
字符串模拟题,我们记录上一个字符为 cur,这个字符出现次数 cnt
- 如果当前字符 c == cur,那么 该字符连续出现次数 ++cnt
- 如果当前字符 c != cur,那么相当于前面连续相同的字符已经结束,那么根据是哪个字符 cur,和对应出现次数 cnt,压缩即可。然后 此时,新的 cur = c,cnt = 1
#include <bits/stdc++.h>
using namespace std;
int main()
{
string s;
cin >> s;
int n = s.size();
string res = "";
int cnt = 1;
char cur = s[0];
for(int i = 1;i < n; ++i)
{
if(s[i] == cur)
{
++cnt;
}
else
{
res += cur;
res += to_string(cnt);
cnt = 1;
cur = s[i];
}
}
res += cur;
res += to_string(cnt);
cout << res << endl;
return 0;
}
试题E:n项和(取模运算)
根据题目,其实就是一个等差数列求和,即 ,根据数据范围,n 是 10^18,用 long long 存储,那么根据取模运算 (a * b) % MOD = (a % MOD) * (b % MOD) % MOD。但是其中还有 / 2,关于除法的取模运算,用到逆元(同时需要用快速幂求)。这里我为了简单,那么通过先将 / 2 运算,那么就需要根据 n 是奇数偶数分不同情况来做即可。
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const LL MOD = 1e9 + 7;
int main()
{
LL n;
cin >> n;
LL res;
if(n % 2 == 0)
{
LL tep = n / 2;
res = (tep % MOD) * ((n + 1) % MOD) % MOD;
}
else
{
LL tep = (n + 1) / 2;
res = (tep % MOD) * (n % MOD) % MOD;
}
cout << res << endl;
return 0;
}
试题F:掉发警告(规律题)
根据题意,我们第一年 = 1,第二年 = 2 (新掉 + 之前掉的),第三年 = 3 (新掉 + 前面掉的), 第四年 = 5 (新掉 + 之前掉的 + 第 4 - 3 年时要多掉的),第五年 = 8 (新掉 + 之前掉 + 第 5 - 3 年时要多掉的 = 1 + 5 + 2 = 8)。
所以,找到规律了,当 表示 第 i 年的掉发,则
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
LL res[60];
int main()
{
int n;
cin >> n;
res[0] = 0;
for(int i = 1;i <= n; ++i)
{
res[i] = res[i - 1] + 1;
if(i - 3 >= 0) res[i] += res[i - 3];
}
cout << res[n] << endl;
return 0;
}
试题G:体前屈大赛(思维)
一开始的想法,每次加入新的数之后,对数组进行排序,这样子就可以得到 第 k 个的值,这样子的时间复杂度是 O(q * nlogn),根据题目的数据范围,最多只能完成 40% 的数据。
但是我们发现,对于每一个数据值的范围是 -1000 到 1000,也就是说,如果我们统计每一个成绩对应的个数,那么从 -1000 到 1000 进行枚举,找到 第 k 个成绩(保证了是升序),那么时间复杂度是 O(q * 2000),不会超时。
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 2500;
int cnt[MAXN];
int main()
{
int n, q;
scanf("%d %d", &n, &q);
int a;
memset(cnt, 0, sizeof(cnt));
for(int i = 0;i < n; ++i)
{
scanf("%d", &a);
++cnt[a + 1000];
}
int t, k;
while(q--)
{
scanf("%d %d", &t, &k);
if(t == 1)
{
++cnt[k + 1000];
}
else
{
int sum = 0;
for(int i = -1000;i <= 1000; ++i)
{
sum += cnt[i + 1000];
if(sum >= k) // 如果发现 >= k,说明,第 k 个值是在这成绩 i 中。
{
cout << i << endl;
break;
}
}
}
}
return 0;
}
试题H:数列第N项(数学 + 快速幂 或者 矩阵快速幂)
方法一、(数学 + 快速幂)
数学推导,得到 an 的通项公式:
根据高中的知识,看到这种结构,会想到构造一个新的数列,为 an 相邻之差,也就是:
根据上式和原式之间的关系,我们可以求出:,这样子就有两种情况的构造
一种是:,我们将
,因此,bn就是一个,等比数列,其中
。所以
一种是 ,我们将
,因此,bn就是一个,等比数列,其中
。所以
所以根据上面两个式子,就可以得到 :
根据取模运算,还有除法的取模(逆元),同时要求幂,用到快速幂即可。
方法二、(矩阵快速幂)
我们根据 ,可以得到以下关系:
,
,.....,
所以,我们要计算,矩阵的幂,假设矩阵的幂结果为 res,那么 :.
因此,我们需要用到矩阵快速幂
方法一代码、(数学 + 快速幂)
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const LL MOD = 1e9 + 7;
LL qpow(LL a, LL b)
{
LL ans = 1;
while(b)
{
if(b & 1) ans = ans * a % MOD;
a = a * a % MOD;
b >>= 1;
}
return ans % MOD;
}
int main()
{
LL n, a1, a2;
cin >> n >> a1 >> a2;
LL c1 = a2 + a1;
LL c2 = a2 - 4 * a1;
LL res = ((qpow(4, n-1) * c1 + qpow(-1, n - 2) * c2 + MOD) % MOD) * qpow(5, MOD - 2) % MOD;
cout << res << endl;
return 0;
}
方法二代码、(矩阵快速幂)
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const LL MOD = 1e9 + 7;
class Mat{
public:
LL nums[2][2];
Mat(){
memset(nums, 0, sizeof(nums));
}
};
int matSize = 2;
Mat& matMul(Mat& a, Mat& b)
{
Mat res = Mat();
for (int i = 0; i < matSize; i++) {
for (int j = 0; j < matSize; j++) {
for (int k = 0; k < matSize; k++) {
res.nums[i][j] = (res.nums[i][j] + a.nums[i][k] * b.nums[k][j] % MOD) % MOD;
}
}
}
a = res;
return a;
}
Mat qPowMat(Mat& a, LL n) // a^n
{
Mat ans = Mat();
for(int i = 0;i < matSize; ++i) // 一开始 ans 是单位矩阵
{
ans.nums[i][i] = 1;
}
while(n)
{
if(n & 1) ans = matMul(ans, a); // ans *= a
a = matMul(a, a); // a *= a
n >>= 1;
}
return ans;
}
int main()
{
LL n, a1, a2;
cin >> n >> a1 >> a2;
if(n == 1)
{
cout << a1 << endl;
}
else if(n == 2)
{
cout << a2 << endl;
}
else
{
Mat a = Mat();
a.nums[0][0] = 3, a.nums[0][1] = 1, a.nums[1][0] = 4;
Mat res = qPowMat(a, n - 2);
LL ans = a2 * res.nums[0][0] + a1 * res.nums[1][0];
cout << ans % MOD << endl;
}
return 0;
}
试题I:耗时种数(最短路)
这道题,就是求最短路,然后判断最短路距离不同的个数(set 去重),根据数据范围,使用最短路算法SPFA即可。
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int INF = 0x3f3f3f3f;
int main()
{
int n, q;
scanf("%d %d", &n, &q);
vector<vector<int> > dist(n, vector<int>(n, INF));
int a, b, c;
for(int i = 0;i < q; ++i)
{
scanf("%d %d %d",&a, &b, &c);
dist[a][b] = min(dist[a][b], c);
}
for(int i = 0;i < n;++i) dist[i][i] = 0;
for(int k = 0;k < n; ++k)
for(int i = 0;i < n;++i)
for(int j = 0;j < n;++j)
dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j]);
unordered_set<int> cnt; // 去重
cnt.clear();
for(int i = 0;i < n; ++i)
{
for(int j = 0;j < n; ++j)
{
if(i == j) continue;
if(dist[i][j] == INF) continue;
cnt.insert(dist[i][j]);
}
}
int ans = cnt.size();
cout << ans << endl;
return 0;
}
试题J:传递信息(思维 + 最短路 + 大数乘法快速幂)
根据一开始的题目分析,我们就是求,从起点 s 到 终点 t 的一个乘积最小。但是看到题目给的每一个节点之间的距离 w 很大,那么这样子乘起来,就会很大(都无法使用 long long 存储)。
但是题目说了,每一个节点之间的距离,一定是 2 的幂,那么距离相乘,变成了 (2)幂的指数相加,这样子,每一个节点之间的距离,最大为 34(因为 2^34 大约就是 10^10)。这样子也就是 起点 s 到 终点 t 的最短路问题(最大为 34 * 5 * 10 ^ 4),可以用 int 存储。
那么将输入的距离,转化为 2 的指数,然后求最短路,这里是求一个正权,单源最短路,就用Dijkstra 算法。
得到最短路之后,我们要输出的值,应该是 2^(最短路),根据最短路可能的最大值是:34 * 5 * 10 ^ 4。同时这个数是无法用 long long 存储的,因此需要用大数(string 模拟),同时如果直接求幂,那会超时,因此要使用大数乘法快速幂(据说用FFT,但是我不会,哈哈哈哈)
下面是我个人的代码,只得到了 80% 的分,也就是,我们最后计算 2^(最短路) 的时候,虽然利用了string 模拟大数乘法,但是没使用快速幂,所以剩下的 20% 数据超时了。(如果要看完整AC代码,可以参考: http://oj.hzjingma.com/solution/detail?id=25378)
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 5e4 + 50;
const int INF = 0x3f3f3f3f;
typedef pair<int, int> PII;
vector<PII> G[MAXN];
int cal2(long long num)
{
int res = 0;
while(num)
{
++res;
num /= 2;
}
return res - 1;
}
string multi(string a, int b) // 大数乘法,用string 模拟
{
string res = "";
int n = a.size();
int c = 0;
for(int i = 0;i < n; ++i)
{
int val = (a[i] - '0') * b + c;
res += char(val % 10 + '0');
//cout << res << endl;
c = val / 10;
}
while(c)
{
res += char(c % 10 + '0');
//cout << res << endl;
c /= 10;
}
return res;
}
int dijkstra(int s, int t, int n) // 表示从起点 s 到 其他点的最短路,总共 n 个点
{
priority_queue<PII, vector<PII>, greater<PII> > pq;
vector<int> dist(n, INF); // 初始化所有的 dist
vector<int> vis(n, 0); // 初始化所有的 vis
dist[s] = 0;
pq.push(make_pair(0, s));
while(!pq.empty()){
int cur = pq.top().second;
pq.pop();
if(vis[cur] == 1) continue;
vis[cur] = 1;
for(int i = 0;i < G[cur].size();++i)
{
int e = G[cur][i].first, w = G[cur][i].second;
if(dist[e] > dist[cur] + w)
{
dist[e] = dist[cur] + w;
pq.push(make_pair(dist[e], e));
}
}
}
// cout << dist[t] << endl;
if(dist[t] == INF) return -1;
else return dist[t];
}
int main()
{
int n, m, s, t;
scanf("%d %d %d %d", &n, &m, &s, &t);
for(int i = 0;i < n; ++i) G[i].clear();
for(int i = 0;i < m;++i)
{
int u, v;
long long w;
scanf("%d %d %lld", &u, &v, &w);
// cout << u << " " << v << " " << cal2(w) << endl;
G[u - 1].push_back(make_pair(v - 1, cal2(w))); // 构造图的时候,有向图,同时距离是 2 的指数
// cout << u << " " << v << " " << cal2(w) << endl;
}
// 最短路,正权
int ans = dijkstra(s - 1, t - 1, n); // 求 两点之间的最短路
if(ans == -1) cout << -1 << endl;
else
{
string res = "1";
for(int i = 0;i < ans; ++i) // 模拟求 2 ^ ans,数据过大,用的是string模拟乘法,但是如果数据更大,那就会超时,所有进一步优化,是要利用FFT求大数乘法。
{
res = multi(res, 2);
}
reverse(res.begin(), res.end());
cout << res << endl;
}
return 0;
}