卡片
题面:
小蓝有很多数字卡片,每张卡片上都是数字0 到9。
小蓝准备用这些卡片来拼一些数,他想从1 开始拼出正整数,每拼一个,就保存起来,卡片就不能用来拼其它数了。
小蓝想知道自己能从1 拼到多少。
例如,当小蓝有30 张卡片,其中0 到9 各3 张,则小蓝可以拼出1 到10,但是拼11 时卡片1 已经只有一张了,不够拼出11。
现在小蓝手里有0 到9 的卡片各2021 张,共20210 张,请问小蓝可以从1拼到多少?
提示:建议使用计算机编程解决问题。
题解:
利用数组将0·9的卡片个数存入数组中,之后每用到一个就减去一个,然后当有一个数字的个数为零的时候输出当前数字,结束程序。
int main()
{
int a[10],t;
for (int i = 0; i < 10; i++)
a[i] = 2021;
for (int i=1;; i++)
{
t = i;
while (t!=0)
{
a[t % 10]--;
if (!a[t%10])
{
cout << i << endl;
return 0;
}
t /= 10;
}
}
return 0;
}
直线
题面:
在平面直角坐标系中,两点可以确定一条直线。
如果有多点在一条直线上,那么这些点中任意两点确定的直线是同一条。
给定平面上2 × 3 个整点{(x, y)|0 ≤ x < 2, 0 ≤ y < 3, x ∈ Z, y ∈ Z},
即横坐标是0 到1 (包含0 和1) 之间的整数、纵坐标是0 到2 (包含0 和2) 之间的整数的点。
这些点一共确定了11 条不同的直线。
给定平面上20 × 21 个整点{(x, y)|0 ≤ x < 20, 0 ≤ y < 21, x ∈ Z, y ∈ Z},
即横坐标是0 到19 (包含0 和19) 之间的整数、纵坐标是0 到20 (包含0 和20) 之间的整数的点。
请问这些点一共确定了多少条不同的直线。
题解:
运用直线的两点式与横截式来求出直线的k和b,存储起来,然后对比不同的k和b并计数。
垂直于x的直线因为没有k而单独计算。
注意精度问题:认定当两个double数的差>1e-8 时二者为不同数<=1e-8时二者为相同的数。
#include<stdio.h>
#include<iostream>
#include<string>
#include<cmath>
#include<vector>
#include<algorithm>
using namespace std;
typedef long long LL;
struct node {
double k, b;
bool operator<(const node& t) {
if (k != t.k)return k < t.k;
else return b < t.b;
}
}l[1000010];
int main()
{
int x1, y1,x2,y2,n=0,ans=1;
for (x1 = 0; x1 < 20; x1++)
for(y1=0;y1<21;y1++)
for(x2=0;x2<20;x2++)
for (y2 = 0; y2 < 21; y2++)
{
if (x1 != x2)
{
double k = (double)(y1 - y2) / (x1 - x2);
double b =y1 - k * x1;
l[n++] = { k,b };
}
}
sort(l, l + n);
for (int i = 1; i < n; i++)
{
if (fabs(l[i].b - l[i - 1].b > 1e-8) || fabs(l[i].k - l[i - 1].k) > 1e-8)
ans++;
}
ans += 20;
cout << ans;
return 0;
}
3.货物摆放
题面:
小蓝有一个超大的仓库,可以摆放很多货物。
现在,小蓝有n 箱货物要摆放在仓库,每箱货物都是规则的正方体。
小蓝规定了长、宽、高三个互相垂直的方向,每箱货物的边都必须严格平行于长、宽、高。
小蓝希望所有的货物最终摆成一个大的立方体。即在长、宽、高的方向上分别堆L、W、H 的货物,满足n = L × W × H。
给定n,请问有多少种堆放货物的方案满足要求。
例如,当n = 4 时,有以下6 种方案:1×1×4、1×2×2、1×4×1、2×1×2、2×2×1、4×1×1。
请问,当n = 2021041820210418 (注意有16 位数字)时,总共有多少种
方案?
提示:建议使用计算机编程解决问题。
题解:
将能被n整除的数存储到a中,再将能被a整除的数存到b中,把结果存到c中,保持a<=b<=c的顺序,避免重复,进行分类讨论。当三个数相等时,只有1种情况,有且仅有两个数相等时,由排列组合得
有3种情况。三个数都不相等时由排列组合得有六种情况。
循环一遍可得出答案。这段代码时间复杂度较高,直接交上去会超时,所以算出答案再printf。
#include<stdio.h>
#include<iostream>
#include<string>
#include<cmath>
#include<vector>
#include<algorithm>
using namespace std;
typedef long long LL;
LL a, b, c, nn, ans, n = 2021041820210418, i, j;
int main()
{
for (i = 1; i * i <= n; i++)
{
if (!(n % i))
{
a = i;
nn = n / i;
for (j = 1; j * j <= nn; j++)
{
if (!(nn % j))
{
b = j;
c = nn / j;
if (a <= b && b <= c)
{
if (a == b && b == c)
ans++;
else if ((a == b && b != c) || (a != b && b == c) || (a == c && b != c))
ans += 3;
else if (a != b && b != c && a != c)
ans += 6;;
}
}
}
}
}
cout << ans;
return 0;
}
4.路径
题面:
小蓝学习了最短路径之后特别高兴,他定义了一个特别的图,希望找到图中的最短路径。
小蓝的图由2021 个结点组成,依次编号1 至2021。
对于两个不同的结点a, b,如果a 和b 的差的绝对值大于21,则两个结点之间没有边相连;
如果a 和b 的差的绝对值小于等于21,则两个点之间有一条长度为a 和b 的最小公倍数的无向边相连。
例如:结点1 和结点23 之间没有边相连;结点3 和结点24 之间有一条无向边,长度为24;
结点15 和结点25 之间有一条无向边,长度为75。
请计算,结点1 和结点2021 之间的最短路径长度是多少。
提示:建议使用计算机编程解决问题。
题解:
最短路模板题。构建邻接矩阵,套Dijkstra模板
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 2022;
int dist[N];
bool st[N];
int main() {
memset(dist, 0x3f, sizeof dist);
memset(st, false, sizeof st);
dist[1] = 0;
for (int i = 1; i < N; i ++) {
int t = -1;
for (int j = 1; j < N; j ++)
if (!st[j] && (t == -1 || dist[j] < dist[t]))
t = j;
st[t] = true;
for (int j = max(t - 21, 1); j <= min(t + 21, N - 1); j ++)
dist[j] = min(dist[j], dist[t] + t / __gcd(t, j) * j);
}
cout << dist[2021] << endl;
}
5.空间
题面:
小蓝准备用256MB 的内存空间开一个数组,数组的每个元素都是32 位二进制整数。
如果不考虑程序占用的空间和维护内存需要的辅助空间,请问256MB 的空间可以存储多少个32 位二进制整数?
题解:
先将MB转换为字节Byte,也就是Byte(B),1MB = 1024KB, 1KB = 1024B,1B = 8bit(位)所以256 MB = 256 * 1024 * 1024B,32位二进制整数的存储空间也即32bit = 32 / 8 = 4B,所以可以存在 256 * 1024 * 1024 / 4,使用程序计算出结果为:67108864
#include<stdio.h>
#include<iostream>
#include<string>
#include<cmath>
#include<vector>
#include<algorithm>
#include<queue>
using namespace std;
typedef long long LL;
int main()
{
printf("%d", 256 * 1024 * 1024 / 4);
return 0;
}
6.砝码称重
题面:
你有一架天平和N 个砝码,这N 个砝码重量依次是W1, W2, ... , WN。
请你计算一共可以称出多少种不同的重量?
注意砝码可以放在天平两边。
输入格式
输入的第一行包含一个整数N。
第二行包含N 个整数:W1, W2, W3, ... , WN。
对于50% 的评测用例,1 ≤ N ≤ 15。
对于所有评测用例,1 ≤ N ≤ 100,N 个砝码总重不超过100000。
输出格式
输出一个整数代表答案。
题解:
用dp法,分析状态和状态转移的过程
#include<stdio.h>
#include<iostream>
#include<string>
#include<cmath>
#include<vector>
#include<algorithm>
#include<queue>
using namespace std;
typedef long long LL;
int n,w[105],sum=0,ans=0;
int dp[102][100005];
int main()
{
cin >> n;
for (int i = 1; i <= n; i++)
{
cin >> w[i];
sum += w[i];
}
dp[0][0] = 1;
for (int i = 1; i <= n; i++)
{
for (int j = 0; j <= sum; j++)
{
dp[i][j] = dp[i - 1][abs(j - w[i])] + dp[i - 1][j + w[i]] + dp[i - 1][j];
}
}
for (int i = 1; i <= sum; i++)
{
if (dp[n][i] != 0)
{
ans++;
}
}
cout << ans;
return 0;
}
7.括号序列
题面:
给定一个括号序列,要求尽可能少地添加若干括号使得括号序列变得合法。
当添加完成后,会产生不同的添加结果,请问有多少种本质不同的添加结果。
两个结果是本质不同的是指存在某个位置一个结果是左括号,而另一个是右括号。
例如,对于括号序列(((),只需要添加两个括号就能让其合法
有以下几种不同的添加结果:()()()、()(())、(())()、(()()) 和((()))。
输入格式
输入一行包含一个字符串s,表示给定的括号序列,序列中只有左括号和右括号。
对于40% 的评测用例,|s| ≤ 200。
对于所有评测用例,1 ≤ |s| ≤ 5000。
输出格式
输出一个整数表示答案,答案可能很大,请输出答案除以1000000007
题解:
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
using LL=long long;
const int N = 5005;
int f[N][N];
int mod=1e9+7;
string s;
int n;
LL get(){
memset(f,0,sizeof f);
f[0][0]=1;
for(int i=1;i<=n;i++){
if(s[i-1]=='('){
for(int j=1;j<=n;j++)
f[i][j]=f[i-1][j-1];
}
else{
f[i][0]=(f[i-1][1]+f[i-1][0])%mod;
for(int j=1;j<=n;j++)
f[i][j]=(f[i-1][j+1]+f[i][j-1])%mod;
}
}
for(int i=0;i<=n;i++)
if(f[n][i])
return f[n][i];
return -1;
}
int main(){
cin>>s;
n=s.size();
LL x=get();
reverse(s.begin(),s.end());
for(int i=0;i<n;i++){
if(s[i]==')')
s[i]='(';
else
s[i]=')';
}
LL y=get();
cout<<(x*y)%mod;
}
8.时间显示
题面:
小蓝要和朋友合作开发一个时间显示的网站。
在服务器上,朋友已经获取了当前的时间,用一个整数表示。
值为从1970 年1 月1 日00:00:00 到当前时刻经过的毫秒数。
现在,小蓝要在客户端显示出这个时间。
小蓝不用显示出年月日,只需要显示出时分秒即可,毫秒也不用显示,直接舍去即可。
给定一个用整数表示的时间,请将这个时间对应的时分秒输出。
输入格式
输入第一行包含正整数T,表示存在T组测试数据,T不超过1000。
接下来T行,每行一个正整数表示时间。时间不超过10^18。
输出格式
输出T行,每行按照如下格式:
输出时分秒表示的当前时间,格式形如HH:MM:SS
其中HH 表示时,值为0 到23,MM 表示分,值为0 到59,SS 表示秒,值为0 到59。
时、分、秒不足两位时补前导0。
题解:
算出每天,每小时,以及每分钟的秒数,把毫秒换为秒,进行求解。
注意数据范围,应该使用longlong类型;
#include<stdio.h>
#include<iostream>
#include<string>
#include<cmath>
#include<vector>
#include<algorithm>
#include<queue>
using namespace std;
typedef long long LL;
void solve()
{
LL s,d=24*60*60,h=60*60,m=60,mm,hh,ss;
cin >> s;
s /= 1000;
s %= d;
hh = s / h;
mm = s % h / m;
ss = s % m;
printf("%02d:%02d:%02d\n", hh, mm, ss);
}
int main()
{
int t;
cin >> t;
while (t--)
solve();
return 0;
}
9.杨辉三角形
题面:
下面的图形是著名的杨辉三角形:
如果我们按从上到下、从左到右的顺序把所有数排成一列,可以得到如下数列:
1, 1, 1, 1, 2, 1, 1, 3, 3, 1, 1, 4, 6, 4, 1, ...
给定一个正整数N,请你输出数列中第一次出现N 是在第几个数?
输入格式
输入包含T行,表示T组测试数据。T不超过10。
每行输入一个整数N,N不超过10^9。
输出格式
对于每组测试数据输出一行表示答案。
题解:
对于同一行,列数越大对应的数值也越大。而且某一行的某一列的值为x,在列数不变的情况下,无论行数怎么变大都不会再出现比x小的数;同理再行数不变的情况下列数怎么变大也不会出现比x小的数。并且得知n小于等于10的0次方时,有效列数为0-16列。因此我们可以一列一列的考虑,由于随着行号的变大,数值时单调递增的,其知道了行号、列号对应的数值也就知道了,于是便可以二分行号,使用二分查找的方法来计算本题。
杨辉三角形的行数符号公差为1的等差数列,故用等差数列求和公式
加上第几列再加上1(因为列从0开始)即可得出该数的位置
#include<stdio.h>
#include<iostream>
#include<string>
#include<cmath>
#include<vector>
#include<algorithm>
#include<queue>
using namespace std;
typedef long long LL;
LL N;
LL C(int a, int b)
{
LL res = 1;
for (LL i = a, j = 1; j <= b; i--, j++)
{
res = res * i / j;
if (res > N)
return res;
}
return res;
}
void solve()
{
cin >> N;
for (int k = 16; k >= 0; k--)
{
LL l = 2 * k, r = max(N, l), mid;
while (l <= r) {
mid = l + (r - l) / 2;
LL CC = C(mid, k);
if (CC == N)
break;
else if (CC > N)
r = mid - 1;
else
l = mid + 1;
}
if (C(mid, k) == N)
{
cout << (mid + 1) * mid / 2 + k + 1 << endl;
break;
}
}
}
int main()
{
int t;
cin >> t;
while (t--)
{
solve();
}
return 0;
}
10.双向排序
题面:
给定序列(a[1], a[2], ... , a[n]) = (1, 2, ... , n),即a[i] = i。
小蓝将对这个序列进行m次操作,每次可能是将a[1], a[2], ... a[qi] 降序排列,或者将a[qi], a[qi+1], ... , a[n] 升序排列。
请求出操作完成后的序列。
输入格式
输入的第一行包含两个整数n, m,分别表示序列的长度和操作次数。
接下来m行描述对序列的操作,其中第i行包含两个整数pi, qi 表示操作类型和参数。
当pi = 0 时,表示将a[1], a[2], ... a[qi] 降序排列;当pi = 1 时,表示将a[qi], a[qi+1], ... , a[n] 升序排列升序排列。
对于30% 的评测用例,n,m ≤ 1000;
对于60% 的评测用例,n,m ≤ 5000;
对于所有评测用例,1 ≤ n,m ≤ 100000, 0 ≤ ai ≤ 1,1 ≤ bi ≤ n。
输出格式
输出一行,包含n个整数,相邻的整数之间使用一个空格分隔,表示操作完成后的序列。
题解:
#include<stdio.h>
#include<iostream>
#include<string>
#include<cmath>
#include<vector>
#include<algorithm>
#include<queue>
using namespace std;
typedef long long LL;
vector<int> st[100005];
int a[100005];
int main()
{
int n, m, top = 0;
cin >> n >> m;
for (int i = 1; i <= m; i++)
{
int u, v;
scanf("%d%d", &u, &v);
if (top == 0 && u == 0)
{
++top;
st[top].push_back(u);
st[top].push_back(v);
}
else if (top)
{
if (u == st[top][0] && (v <= st[top][1] && u == 0 || v >= st[top][1] && u == 1))
continue;
else
{
if (u == 0 && u == st[top][0] && v > st[top][1])
st[top--].clear();
if (u == 1 && u == st[top][0] && v < st[top][1])
st[top--].clear();
if (u == 0)
while (top > 1 && st[top - 1][1] <= v)
{
st[top--].clear();
st[top--].clear();
}
else
while (top > 1 && st[top - 1][1] >= v)
{
st[top--].clear();
st[top--].clear();
}
++top;
st[top].push_back(u);
st[top].push_back(v);
}
}
}
int temp = n;
int l = 1, r = n;
for (int i = 1; i <= top; i++)
{
if (i & 1)
{
for (int j = r; j > st[i][1]; j--)
if (temp)
a[j] = temp--;
r = st[i][1];
}
else
{
for (int j = l; j < st[i][1]; j++)
if (temp)
a[j] = temp--;
l = st[i][1];
}
}
if (top & 1)
for (int i = l; i <= r; i++)
a[i] = temp--;
else
for (int i = r; i >= l; i--)
a[i] = temp--;
for (int i = 1; i <= n; i++)
printf("%d ", a[i]);
return 0;
}