A 卡片
简单思维
#include<bits/stdc++.h>
using namespace std;
const int INF = 1e9;
int mp[15];
bool flag;
int ans;
void get(int x){
while(x){
int a = x % 10;
mp[a] ++ ;
if(mp[a] == 2021){
flag = true;
break;
}
x /= 10;
}
}
int main()
{
for(int i = 1; i <= INF; i ++ ){
get(i);
if(flag){
ans = i;
break;
}
}
// for(int i = 0; i <= 9; i ++ ){
// cout<<i<<' '<<mp[i]<<endl;
// }
//
cout<<ans<<endl;
return 0;
}
B 直线
简单模拟题,需要注意的是一元二次方程求k、b的公式和要记住坐标用double类型存,否则算出来的k、b都是int类型,会直接影响结果
思路:每条直线的唯一标识就是斜率k和截距b,遍历所有两个点的组合,所以用map保存这两个数据,记录直线是否出现过,同时计数
#include<bits/stdc++.h>
using namespace std;
#define db double
const int N = 500;
typedef pair<db, db>PDD;
PDD p[N];
int ans; //记录
map<PDD, double>mp;
int res; //有几个点
int main()
{
for(int x1 = 0; x1 <= 19; x1 ++ ){
for(int y1 = 0; y1 <= 20; y1 ++ ){
p[res ++ ] = {x1, y1};
}
}
// cout<<res<<endl;
for(int i = 0; i < res; i ++ ){
for(int j = 0; j < res; j ++ ){
if(p[i].first == p[j].first || p[i].second == p[j].second) continue;
db k = (p[j].second - p[i].second) / (p[j].first - p[i].first);
db b = (p[i].second * p[j].first - p[j].second * p[i].first) / (p[j].first - p[i].first);
if(mp[{k, b}] == 0){
ans ++ ;
mp[{k, b}] = 1;
}
}
}
ans += 41;
cout<<ans<<endl;
return 0;
}
C 货物摆放
货物摆放
这道题就是暴力,不断剪枝,先找到第一个可以被n整除的i,然后让n / i得n1,之后再找到能被n1整除的第数j,之后让n1 / j得k,此时i * j * k就是n,当三者都相等的时候,只有一种排列方式,方案数+1,当其中两者相等时,有三种排列方式,方案数+3, 当三者都不等的时候,有六种排列方式,方案数+6
这种方法交代码肯定会超时,所以直接交答案即可
得出答案的代码(超时版):
#include<bits/stdc++.h>
using namespace std;
#define int long long
int n = 2021041820210418;
int ans;
signed main()
{
for(int i = 1; i <= sqrt(n); i ++ ){
int n1 = n / i;
if(n % i == 0){
for(int j = 1; j <= sqrt(n1); j ++ ){
if(n1 % j == 0){
int k = n1 / j;
if(i <= j && j <= k){
if(i == j && j == k){
ans ++ ;
}
else if(i == j && j != k){
ans += 3;
}
else if(i != j && j == k){
ans += 3;
}
else if(i == k && j != k){
ans += 3;
}
else if(i != j && j != k){
ans += 6;
}
}
}
}
}
}
cout<<ans<<endl;
// cout<<2430<<endl;
return 0;
}
答案代码:
#include<bits/stdc++.h>
using namespace std;
#define int long long
int n = 2021041820210418;
int ans;
signed main()
{
cout<<2430<<endl;
return 0;
}
D 路径
学会了最小公倍数的求法,复习了最大公约数的求法
#include<bits/stdc++.h>
using namespace std;
const int N = 2025;
int dis[N];
int gcd(int a, int b){
if(!b) return a;
else return gcd(b, a % b);
}
int lcm(int a, int b){
return (a * b) / gcd(a, b);
}
int main()
{
for(int i = 1; i <= 22; i ++ ){
dis[i] = i;
}
for(int i = 23; i <= 2021; i ++ ){
int j = 21;
dis[i] = dis[i - j] + lcm(i - j, i);
while(j > 0){
if(dis[i] > dis[i - j] + lcm(i - j, i))
dis[i] = dis[i - j] + lcm(i - j, i);
j -- ;
}
}
cout<<dis[2021]<<endl;
return 0;
}
E 空间
复习了一个int占4个字节byte,一个字节有8位
#include<bits/stdc++.h>
using namespace std;
const int N = 2025;
int main()
{
cout<<256 * 1024 * 1024 / 4<<endl;
return 0;
}
F 砝码称重
dp问题
大佬题解原地址
#include<bits/stdc++.h>
using namespace std;
const int N = 105, M = 1e5 + 10;
int dp[N][M];
int n;
int w[N];
int sum;
int ans;
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] = max(dp[i - 1][j], max(dp[i - 1][j + w[i]], dp[i - 1][abs(j - w[i])]));
}
}
for(int i = 1; i <= sum; i ++ ){
ans += dp[n][i];
}
cout<<ans<<endl;
return 0;
}
H 括号序列
借鉴大佬的题解
题解原地址
几个需要清楚的问题:
①关于括号序列合法性:对于一段括号序列,从左往右起,一个字符一个字符的往前看,对于每一段小的括号序列 --‘(’-- 数量 大于等于 --‘)’-- 数量,那么整个括号序列就合法。
②关于 --‘(’-- 和 --’)’-- 的添加可以分开来讨论:括号是被添加到原序列的括号与括号之间的空隙里的,假如左括号和右括号加入的是不同的空隙,那么它们必然是互不影响的。如果加入的是同一个空隙,那么右括号的添加必然在左括号之前,否则括号配对,添加无意义,不存在顺序的影响,那么也是互不影响的,所以我们将其的添加分开讨论。
③上面第二点清楚了之后,如何利用其解决问题:可以分开讨论之后,我们可以判断在原括号序列添加左括号使之变得合法,然后变换原括号序列(先将括号序列逆序,再将左括号变成右括号,右括号变成左括号),这样调整之后我们在变换后的括号序列中添加左括号使之变得合法(相当于在原括号序列添加右括号)。
下面是举例解释:
通常我们只需要添加一种括号就能使整个括号序列合法。
例如
原括号序列:((() 左括号数量大于等于右括号数量,合法,不用添加
变换后序列:())) 左括号数量小于右括号数量,不合法,需要添加
但也有特殊情况,需要两种括号都加
原括号序列:) ) ( ( 乍一看好像相等,理解了第一点就知道此序列不合法,需要添加。
变换后序列:) ) ( ( 和原括号序列一样,不合法,需要添加。
这样一来我们的问题变成了在括号序列中添加左括号使合法的问题,所以每碰到一个右括号,我们就可以在其前边添加左括号,从刚好合法(左右括号相等)到更多的左括号(上限是括号序列长度)。
④dp数组的含义:之前看题解都写的一样,但是那样想我感觉始终有些问题想不明白。
自己想了一种含义(其实和原含义差不多,但是更好理解了):
dp[i][j]是指前i个括号字符之前 添加不知道多少个(可以是0个) --’(’-- 使这前i个括号字符合法(合法的含义又是 --’(’-- 比 --’)’-- 多或者相等,所以j从0开始) 的种数。
可能有点拗口…去掉括号注释来看就是:前i个括号字符之前添加不知道多少个左括号使前i个括号字符合法的种数。
也就是说加多少个我们是不管的,我们只考虑添加后的结果中左括号比右括号多多少个。而j是下标必定大于等于0,于是如果dp[i][j]这个状态是可以存在的那么这个值一定不是0,也就是不可能是0种。
⑤递推公式:
遇到 --‘(’-- :我们只考虑在 --‘)’-- 前添加 --‘(’-- 使这个右括号之前的括号序列合法。遇到左括号的时候,证明在这个左括号之前的括号序列已经被判断过如何使其合法了,那么加上这个左括号依然合法,所以我们不需要管这个左括号。也就是在他前面的括号序列添加左括号使其合法的种数等于加上这个左括号之后这个序列需要添加的左括号种数。
dp[i][j]=dp[i-1][j-1];
遇到 --‘)’-- :如果这个加上这个右括号的序列本身合法,那么我们仅需添加0个左括号就能使其合法,如果不合法就需要添加刚好使得其合法的左括号甚至可以更多。
dp[i][j] = dp[i-1][0] + dp[i-1][1] + … + dp[i-1][j] + dp[i-1][j+1]
解释:
要得到前i个字符的序列左括号比右括号多j个的合法情况
可以由
前i-1个字符的序列左括号比右括号多0个(刚好合法)的情况,再在这个右括号前面加j+1个左括号得到
前i-1个字符的序列左括号比右括号多1个的情况,再在这个右括号前面加j个左括号得到
.
.
.
前i-1个字符的序列左括号比右括号多j+1个的情况,再在这个右括号前面加0个左括号得到
(其实就是左括号数在这些右括号之间分配的种数)
可上面那样做一定会超时
然后注意到
dp[i][j-1] = dp[i-1][0] + dp[i-1][1] + … dp[i-1][j]
∴dp[i][j] = dp[i-1][j+1] + dp[i][j-1]
和多重背包类似用到了当前行的状态
初始化:dp[0][0]=1 这是显而易见的
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 5010, mod = 1e9 + 7;
int dp[N][N];
char str[N];
int n;
int calc(){
memset(dp, 0, sizeof dp);
dp[0][0] = 1;
for(int i = 1; i <= n; i ++ ){
if(str[i] == '('){
for(int j = 1; j <= n; j ++ ){
dp[i][j] = dp[i - 1][j - 1];
}
}
else{
dp[i][0] = (dp[i - 1][1] + dp[i - 1][0]) % mod;
for(int j = 1; j <= n; j ++ ){
//dp[i][j]表示前i个字符,左括号的数量比右括号的数量多j个的方案数
//在遇到这个右括号之前,左括号的数量是存在比右括号多j+1、j、j-1...1的状态
//j、j-1...1这些状态蕴含在dp[i][j - 1]
//所以dp[i][j]的状态是由这两组状态转移来的
dp[i][j] = (dp[i - 1][j + 1] + dp[i][j - 1]) % mod;
}
}
}
for(int i = 0; i <= n; i ++ ){
if(dp[n][i]) return dp[n][i];
}
return -1;
}
signed main()
{
scanf("%s", str + 1);
n = strlen(str + 1);
int l = calc();
reverse(str + 1, str + n + 1);
for(int i = 1; i <= n; i ++ ){
if(str[i] == '(') str[i] = ')';
else str[i] = '(';
}
int r = calc();
int iu = l * r % mod;
cout<<iu<<endl;
return 0;
}
I 时间显示
简单输出时间转换,思路不清楚导致写的慢了一些
#include<bits/stdc++.h>
using namespace std;
#define int long long
int n;
int t, hh, m, s;
int T;
signed main()
{
cin>>T;
while(T -- ){
cin>>n;
n /= 1000;
//一天的毫秒数是86400000
if(n > 86400) t = n % 864000;
else t = n;
hh = t / 3600 % 24; //小时
int m = t / 60 % 60 ; //分钟
int s = t % 60; //秒
if(hh >= 10) cout<<hh;
else cout<<"0"<<hh;
cout<<":";
if(m >= 10) cout<<m;
else cout<<"0"<<m;
cout<<":";
if(s >= 10) cout<<s;
else cout<<"0"<<s;
cout<<endl;
}
return 0;
}
J 杨辉三角形
是个思维难题,记住杨辉三角的规律,每一行最中间的数最大,第k行最中间的数是kC2k
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int T;
int n;
int 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) break;
}
return res;
}
bool check(int k){
int l = 2 * k;
int r = max(l, n);
while(l < r){
int mid = l + r >> 1;
if(C(mid, k) >= n) r = mid;
else l = mid + 1;
}
if(C(r, k) != n) return false;
cout<<1ll * r * (r + 1) / 2 + k + 1<<endl;
return true;
}
int main()
{
cin>>T;
while(T -- ){
cin>>n;
for(int i = 16; i >= 0; i -- ){
if(check(i)) break;
}
}
return 0;
}
K 双向排序
比较难的一个思维题,y总讲之前从没想过可以这样操作区间
大佬题解原地址
#include<bits/stdc++.h>
using namespace std;
#define x first
#define y second
const int N = 1e5 + 10;
typedef pair<int, int>PII;
PII stk[N];
int ans[N];
int n, m;
int main()
{
cin>>n>>m;
int top = 0;
while(m -- ){
int p, q;
cin>>p>>q;
if(!p){
while(stk[top].x == 0 && top) q = max(stk[top -- ].y, q); //找到当前栈中连续的前缀操作中最大的范围
while(stk[top - 1].y <= q && top >= 2) top -= 2; //弹出栈内的所有小区间
stk[ ++ top] = {0, q}; //将目前最大的前缀操作放入栈中
}
else if(top){
while(stk[top].x == 1 && top ) q = min(stk[top -- ].y, q);
while(stk[top - 1].y >= q && top >= 2) top -= 2;
stk[ ++ top] = {1, q};
}
}
int k = n, l = 1, r = n;
for(int i = 1; i <= top; i ++ ){
if(stk[i].x == 0){ //前缀操作
while(stk[i].y < r && l <= r) ans[r -- ] = k -- ;
}
else{
while(stk[i].y > l && l <= r) ans[l ++ ] = k -- ;
}
if(l > r) break;
}
if(top % 2){
while(l <= r) ans[l ++ ] = k -- ;
}
else{
while(l <= r) ans[r -- ] = k -- ;
}
for(int i = 1; i <= n; i ++ ){
cout<<ans[i]<<' ';
}
cout<<endl;
return 0;
}