2、卡片
笔记:
直接巧用排列组合求解即可:
我们通过对样例说明进行分析可知:想要分给n个小孩,那么我们就需要满足C(K, 2) + K >= n才能满足。
cpp
#include<bits/stdc++.h>
using namespace std;
int com(int up, int down){
if(up > down) return 0;
if(up == down) return 0;
int res = 1;
for(int i = 1; i <= up; i++){
res = res * (down - i + 1) / i;
}
return res;
}
int main(){
int n;
cin >> n;
int down = 2;
while(com(2, down) + down < n){
down++;
}
cout << down << endl;
return 0;
}
4、货物摆放:
题目:
小蓝有一个超大的仓库,可以摆放很多货物。
现在,小蓝有 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太大了,所以我们需要将预处理数据的大小压缩一下,因为n = l * h * w,所以我们可以只求l,h,w的组合,为了不在从0开始一个一个得去找找三个符合的数,我们可以直接去获取n的所有因子的数列,再从这个数列中去找到符合的l,h,w的值。
cpp
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
vector<ll> res(1e7, 0);
ll n = 2021041820210418;
int main(){
ll index = 0;
for(ll i = 1; i <= sqrt(n); i++){
if(n % i == 0){
res[index++] = i;
if(i * i != n){
res[index++] = n / i;
}
}
}
res.resize(index + 1);
ll count = 0;
for(ll l = 0; l < res.size(); l++){
for(ll h = 0; h < res.size(); h++){
for(ll w = 0; w < res.size(); w++){
if(res[l] * res[h] * res[w] == n){
count++;
}
}
}
}
cout << count;
return 0;
}
3、直线
题目:
在平面直角坐标系中,两点可以确定一条直线。如果有多点在一条直线上, 那么这些点中任意两点确定的直线是同一条。
给定平面上 2 × 3 个整点(�,�)∣0≤�<2,0≤�<3,�∈�,�∈�(x,y)∣0≤x<2,0≤y<3,x∈Z,y∈Z,即横坐标 是 00 到 11 (包含 0 和 1) 之间的整数、纵坐标是 0 到 2 (包含 0 和 2) 之间的整数 的点。这些点一共确定了 11 条不同的直线。
给定平面上 20×2120×21 个整点 (�,�)∣0≤�<20,0≤�<21,�∈�,�∈�(x,y)∣0≤x<20,0≤y<21,x∈Z,y∈Z,即横 坐标是 00 到 1919 (包含 00 和 1919) 之间的整数、纵坐标是 00 到 2020 (包含 00 和 2020) 之 间的整数的点。
请问这些点一共确定了多少条不同的直线。
笔记:
这道题我们就遵循一个原则:两点确定一条直线,我们只需要找到斜率是独一无二的直线即可,有公式y = k*x + b我们可以获取两点连成直线的斜率以及b,接着就暴力搜索出符合的(k, b),这里我们需要注意的是要对这些组合进行去重,我们可以使用set容器来去重,最后我们还需要注意:还存在斜率不存在的直线也就是平行于x轴的直线,最后就是将set容器的大小再加上这些平行于x轴的直线的数组即可。
cpp
#include<bits/stdc++.h>
using namespace std;
set<pair<double, double> > st;
int main(){
for(int x1 = 0; x1 < 21; x1++){
for(int y1 = 0; y1 < 20; y1++){
for(int x2 = x1 + 1; x2 < 21; x2++){
for(int y2 = 0; y2 < 20; y2++){
double k = (double)(y1 - y2) / (x1 - x2);
double b = (double)(x1 * y2 - x2 * y1) / (x1 - x2);
st.insert(make_pair(k, b));
}
}
}
}
cout << st.size() + 21;
return 0;
}
7、砝码称重:
题目:
你有一架天平和 �N 个砝码,这 �N 个砝码重量依次是 �1,�2,⋅⋅⋅,��W1,W2,⋅⋅⋅,WN。
请你计算一共可以称出多少种不同的重量? 注意砝码可以放在天平两边。
输入格式
输入的第一行包含一个整数 �N。
第二行包含 �N 个整数:�1,�2,�3,⋅⋅⋅,��W1,W2,W3,⋅⋅⋅,WN。
输出格式
输出一个整数代表答案。
样例输入
3
1 4 6
样例输出
10
样例说明
能称出的 1010 种重量是:1、2、3、4、5、6、7、9、10、111、2、3、4、5、6、7、9、10、11。
1=1;1=1;
2=6−4(2=6−4(天平一边放 66,另一边放 4);4);
3=4−1;3=4−1;
4=4;4=4;
5=6−1;5=6−1;
6=6;6=6;
7=1+6;7=1+6;
9=4+6−1;9=4+6−1;
10=4+6;10=4+6;
11=1+4+6。11=1+4+6。
笔记:
这道题可以采用bfs的思想与背包思想相结合:
我们在这里将dp数组设为:前i个元素能否凑成j的bool值,将dp[0][0]初始化为0,接着然后我们遍历dp数组,如果dp[i - 1][j] = true呢么i个元素也一定可以凑成j,所以dp[i][j] = true,然后开始扩散:dp[i][j + nums[i]] = true,dp[i][j - nums[i]] = true,dp[i][nums[i] - j] = true;
扩散完一遍,我们就可以对数组进行检查,如果dp[i][j] = true那么就count ++。
有几点需要注意的是:
(1)这里我们在输入nums数组的时候不可以从0开始加入元素,因为为了符合后续dp数组的遍历:i表示前i个元素,所以我们的nums数组要从1开始。
(2)在dp数组中进行遍历搜索的时候我们无需提前判断当前dp[i][j]是否为false,因为我们是符合了bfs一层一层向外扩散,所以我们不单单是对fasle的dp元素进行判断处理,对true的dp元素也一样要判断处理。
cpp
#include<bits/stdc++.h>
using namespace std;
int main(){
int n;
cin >> n;
vector<int> nums(n + 1, 0);
int sum = 0;
int count = 0;
for(int i = 1; i <= n; i++){
cin >> nums[i];
sum += nums[i];
}
vector<vector<bool> > dp(n + 1, vector<bool>(sum + 1, false));
dp[0][0] = true;// 前i中元素是否能够凑成j
for(int i = 1; i <= n; i++){
for(int j = 0; j <= sum; j++){
if(dp[i][j] == false){
if(dp[i - 1][j] == true){
dp[i][j] = true;
dp[i][j + nums[i]] = true; // 会略过nums[0]的情况。
if(j > nums[i]){
dp[i][j - nums[i]] = true;
}else{
dp[i][nums[i] - j] = true;
}
}
}
}
}
for(int j = 1; j <= sum; j++){
if(dp[n][j]) count++;
}
cout << count;
return 0;
}
10、括号序列:
题目:
笔记:
(1)首先明确dp数组含义:
dp[ i ][ j ]:表示第i个右括号前添加j个左括号的组合数。
(2)状态转移方程:
dp[ i ][ j ] = dp[ i - 1 ][ 0 ] + ......... + dp[ i - 1 ][ j ]:
也就是i-1号有括号前加0个左括号,i - 1号右括号到i好有括号之间加j个左括号 到i - 1号有括号前加j个左括号, i- 1号右括号到i号右括号之间加0个左括号。
通过对上面状态转移方程的进一步分析我们就可以发现并不能从0开始遍历,我们的i的遍历范围是从第一个到第n个,那我们j的遍历范围就应该是从(i - suml(前面剩余的左括号的数量) ----- j)也就是目的是让前面的合法。
(3)初始化:
按照我们定义的含义来分析:首先看第一个右括号前面我们如没有剩余的左括号,那么添加1 - j的左括号的方案数都为1,若果有剩余左括号那么前面添加0个左括号的方案数为1。