试题 A: 空间
直接用计算器算。
每个元素,32bit,也就是4Byte
256 ∗ 1024 ∗ 1024 / 4 = 67 , 108 , 864 256*1024*1024/4=67,108,864 256∗1024∗1024/4=67,108,864
所以答案是67108864
试题 B: 卡片
答案:3181
思路:模拟一下。
代码:
/*
3181
*/
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
int s[10]; //手上的库存
bool check(int num) {
//取每一位
while(num) {
int t = num%10;
if(--s[t] < 0) return false;
num/=10;
}
return true;
}
int main() {
for(int i = 0; i < 10; i++) s[i] = 2021;
for(int i = 1; ;i++) {
if(!check(i)) { //如果拼不了了
cout << i-1 << endl; //注意返回拼不了的前一个
return 0;
}
}
return 0;
}
试题 C: 直线
思路:通过斜率和截距可以唯一确定一条直线,也就是 y = k x + b y=kx+b y=kx+b 。
所以我们可以枚举所有点对能够构成直线的斜率k和截距b,然后判断有多少对不同的k和b。
特殊情况:当直线是垂直的时候,斜率不存在,所以需要特判垂直的直线。那么所有垂直的线都不枚举,最后给它直接加上个20就可以了(总共有20条垂线)。
由于浮点数的不准确性,所以不能直接判断k、b是否相等,而是需要判断是否在一个足够小的范围内(通常就设定为1e-8以内)。
答案:40257
代码:
/*
40257
*/
#include <cstdio>
#include <cstring>
#include <cmath>
#include <iostream>
#include <algorithm>
using namespace std;
//最多有420个点,取个平方176,400
const int N = 200000;
int n; //记录了n条线
struct Line{
double k,b;
//做个自定义升序排序
bool operator< (const Line& t) const {
if(k != t.k) return k < t.k;
return b < t.b;
}
}l[N];
int main() {
for(int x1 = 0; x1 < 20; x1++)
for(int y1 = 0; y1 < 21; y1++)
for(int x2 = 0; x2 < 20; x2++)
for(int y2 = 0; y2 < 21; y2++) {
if(x1==x2) continue;//跳过垂直线
double k = (double)(y2-y1)/(x2-x1);
double b = y1 - k*x1;
l[n++] = {k,b};
}
sort(l,l+n);
int ans = 1;
for(int i = 1; i < n; i++)
if(fabs(l[i].k - l[i-1].k) > 1e-8 ||
fabs(l[i].b - l[i-1].b) > 1e-8) //说明不是一条线
ans++;
ans += 20; //加上没有斜率的20条垂线
cout << ans << endl;
return 0;
}
试题 D: 货物摆放
思路:找 n = a ∗ b ∗ c n=a*b*c n=a∗b∗c的方案数,考虑顺序。
方法一:纯暴力(等几秒出结果)
方法二:求n的所有约数,三重循环枚举所有选择,其实也很暴力。
答案:2430
方法一纯暴力代码:
/*
2430
纯暴力
*/
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long LL;
int main() {
LL n = 2021041820210418;
int res = 0;
//先找出n=i*j*k的所有的组合
for(LL i = 1; i*i*i <= n; i++) { //i从1遍历到3次根号n
if(n%i != 0) continue; //i不是约数跳过
for(LL j = i; j*j <= n/i; j++) { //i已经确定,j*j<=n/i
if(n/i%j != 0) continue; //i确定的情况下,j不是约数跳过
LL k = n/i/j; //计算求得k
//i,j,k是一组,k一定是约数
if(i==j&&j==k) res++; //三个数相等加1种方案
else if(i!=j && j!=k) res+=6; //三个数不相等加6种方案,P33 = 6
else res+=3; //两个数相等加3种方案 ,P33-(P22-1)*x=x => x=3
}
}
cout << res << endl;
return 0;
}
方法二代码:
/*
2430
*/
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
typedef long long LL;
vector<LL> d;
int main() {
LL n = 2021041820210418;
//存n的所有约数
for(LL i = 1; i*i <= n; i++) {
if(n%i == 0) {
d.push_back(i); //存约数
if(n/i != i) d.push_back(n/i); //存相对的约数
}
}
int res = 0;
for(auto a : d)
for(auto b : d)
for(auto c : d)
if(a*b*c == n) res++;
cout << res << endl;
return 0;
}
试题 E: 路径
思路:经典最短路问题,用迪杰斯特拉、SPFA、佛洛依德都可以。我这里用迪杰斯特拉算法
答案:10266837
代码:
/*
10266837
*/
#include <cstdio>
#include <cstring>
#include <cmath>
#include <climits>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long LL;
const int N = 2030, INF = 0x3f3f3f3f;
int n;
int e[N][N], dist[N];
bool st[N];
int gcd(int a, int b) {
return b ? gcd(b,a%b) : a;
}
int lcm(int a, int b) {
int c = gcd(a,b);
return a*b/c;
}
int main() {
n = 2021;
//初始化邻接矩阵
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= n; j++) {
if(i == j) e[i][j] = 0;
else if(abs(i-j) <= 21) e[i][j] = lcm(i,j);
else e[i][j] = INF;
}
}
//dist是1到个各节点的初始路程
for(int i = 1; i <= n; i++) dist[i] = e[1][i];
st[1] = true;
for(int i = 1; i <= n - 1; i++) { //一共需要找n-1次最近的结点
int u = -1; //距离1最近的结点编号
//找最近
for(int j = 1; j <= n; j++)
if(!st[j] && (u == -1 || dist[j] < dist[u]))
u = j;
st[u] = true; //u已经处理完毕
//通过u更新1到各结点的最近距离
for(int v = 1; v <= n; v++)
dist[v] = min(dist[v], dist[u] + e[u][v]);
}
cout << dist[n] << endl;
return 0;
}
试题 F: 时间显示
思路:注意要舍去年月日,取时分秒。
代码:
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long LL;
int main() {
LL n;
cin >> n;
n /= 1000; //舍去毫秒,即舍去最后3位
n %= 86400; //取时分秒,24*60*60=86,400秒
int h = n / 3600; //取小时,一小时60*60=3600秒
int m = n % 3600 / 60; //取分钟
int s = n % 60; //取秒
printf("%02d:%02d:%02d\n",h,m,s);
return 0;
}
试题 G: 砝码称重
思路:背包问题,用DP。比赛的时候用dfs暴力应该只得了部分分。
分析一下,假设物品放右边,Wi是每个砝码的重量,Ci是该砝码当前状态下的取值,那么每个砝码有3种状态,不放(Ci=0),放左边(Ci=Wi),放右边(Ci=-Wi)。
状态表示:f[i][j]
表示为 前 i 个砝码中选,重量为 j 的情况是否可以称出来。
状态计算:最后一个砝码有三种情况:不选,取正值,取负值。那么3种情况回溯到 i - 1,有一种为真f[i][j]
就为真。
转移方程:f[i][j]= f[i-1][j] | f[i-1][j-w[i]] | f[i-1][j+w[i]]
初始化:f[0][0] = true
,啥也不放,重量为0,肯定可以称出来。
DP做法代码:
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 105, M = 200010;
int n,m;
int w[N];
bool f[N][M];
int main() {
cin >> n;
for(int i = 1; i <= n; i++) scanf("%d",&w[i]), m += w[i];
f[0][0] = true;
for(int i = 1; i <= n; i++) {
for(int j = 0; j <= m; j++) {
//如果遇到一个负重量,把它归于它的正重量。
f[i][j] = f[i-1][j] | f[i-1][abs(j-w[i])] | f[i-1][j+w[i]];
}
}
int res = 0;
for(int j = 1; j <= m; j++)
if(f[n][j]) res++;
cout << res << endl;
return 0;
}
DFS暴力做法代码(TLE,过部分样例):
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
int n,res;
int w[100010];
bool st[100010];
void dfs(int k,int m) { //k个的砝码,重量是sum
if(k > n) {
if(m>0 && !st[m]) {
res++;
st[m]=true;
}
return;
}
//还没选够n个砝码
dfs(k+1,m);//不选
dfs(k+1,m+w[k]);//放左边,+wi
dfs(k+1,m-w[k]);//放右边,-wi
}
int main() {
cin >> n;
for(int i=1; i<=n; i++) scanf("%d",&w[i]);
dfs(0,0);
cout << res << endl;
return 0;
}
试题 H: 杨辉三角形
![image-20210511114354706](http://wangpeng-imgsubmit.oss-cn-hangzhou.aliyuncs.com/img/20210511121213.png)
思路:
因为对称,所以只看左半边。
![image-20210511114434265](http://wangpeng-imgsubmit.oss-cn-hangzhou.aliyuncs.com/img/20210511121215.png)
斜着看,枚举每个斜行,因为每个斜行里是单调的,所以可以用二分查找,一个斜行里找不到就换下一个斜行找。
n最大1e9,C(34, 17) > 1e9, C(32, 16) < 1e9,因此只要枚举前16个斜行即可。
设第k个斜行,该斜行的每个元素为C(2k,k),C(2k+1,k),C(2k+2,k)…
当k等于1的时候,cnk一定有解,所以r等于n就行了。
crk,r前面有r行,第1行一个数,第二行2个数,是等差数列,r(1+r)/2,然后k加1
然后注意图上有2个6,更内行的6才是答案,所以行要从16往小遍历。
这题比赛时也只能暴力了。
代码:
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long LL;
int n;
//计算组合数,a * (a-1) .. b个 / b!
LL c(int a, int b) {
LL res = 1;
for (int i = a, j = 1; j <= b; i--, j++) {
res = res * i / j;
if (res > n) return res;
}
return res;
}
//检查第k行
bool check(int k) {
LL l = 2 * k, r = max((LL)n, l);
while (l < r) {
LL mid = l + r >> 1;
if (c(mid, k) >= n) r = mid;
else l = mid + 1;
}
if (c(r, k) != n) return false;
// C(r, k)的从0开始的顺序
cout << r * (r + 1) / 2 + k + 1 << endl;
return true;
}
int main() {
cin >> n;
for (int k = 16; ; k--) {
if (check(k)) break;
}
return 0;
}
试题 I: 双向排序
![image-20210511114257925](http://wangpeng-imgsubmit.oss-cn-hangzhou.aliyuncs.com/img/20210511121216.png)
思路:模拟骗分吧。
代码:
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 100010;
int a[N];
int main() {
int n,m;
cin >> n >> m; //长度,操作次数
for(int i = 1; i <= n; i++) a[i] = i; //初始化
while(m--) {
//类型p,参数q
//0,a[1~q]降序 1,a[q~n]升序
int p,q;
scanf("%d%d",&p,&q);
if(p==0) {
sort(a+1,a+q+1,greater<int>());
}else {
sort(a+q,a+n+1);
}
}
for(int i = 1; i <=n ; i++) printf("%d ",a[i]);
return 0;
}
试题 J: 括号序列
没做。