9.22 周二
学习一定要清楚地知道自己学了什么,用这个博客记录一下自己究竟学了什么,不要一塌糊涂
算是一个反思总结吧。做题不要盲目做,做完花个5分钟反思总结一下到底学到了什么,列个一二三,这样印象更深。以后也可以回来复习
一道题一道题扎扎实实地独立做出并总结,这样稳步提升。算是费曼学习法的实践,把题目讲出来。
这个博客算是写给自己看的。
两年没碰了,现在阶段算是恢复
慢慢建立起学习体系和方法
洛谷 P2181 对角线
#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
typedef unsigned long long ull;
int main()
{
ull a;
scanf("%llu", &a);
printf("%llu", a * (a-1) / 2 * (a-2) / 3 * (a-3) / 4);
return 0;
}
一 没有独立做出。这次找了个借口说我现在在恢复而不是干数学。数学部分没想太多,就看了题解。这样非常不好,每一次写题都要独立写出,不要找借口。至少刚两天还做不出才看题解
二 超时的话算一下在10的九次方内就可以
三 这个公式计算会爆,这是这道题难点。
首先是不要一下全部乘完,而是一边乘一边除,而且判断可以除尽
然后是变量类型。
int 1e9
long long 1e18
unsigned long long 1e19
题目1e5,用公式算出大概是1e19
所以要用unsigned long long scanf和printf用llu
int gcd(int a, int b) { return !b ? a: gcd(b, a % b); }
复习gcd
9.23 周三
洛谷 P5740 【深基7.例9】最厉害的学生
#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
const int MAXN = 1000 + 10;
struct node
{
char name[10];
int a, b, c;
}in[MAXN];
int main()
{
int n, sum = 0, ans;
scanf("%d", &n);
REP(i, 0, n)
{
scanf("%s%d%d%d", in[i].name, &in[i].a, &in[i].b, &in[i].c);
if(in[i].a + in[i].b + in[i].c > sum)
{
sum = in[i].a + in[i].b + in[i].c;
ans = i;
}
}
printf("%s %d %d %d\n", in[ans].name, in[ans].a, in[ans].b, in[ans].c);
return 0;
}
复习结构体
把之前熟悉的知识一点点捡起来
洛谷 P5723 【深基4.例13】质数口袋
#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int MAXN = 1e5 + 10;
bool prime[MAXN];
void init()
{
memset(prime, true, sizeof(prime));
prime[0] = prime[1] = false;
REP(i, 2, MAXN)
if(prime[i])
for(int j = i * 2; j < MAXN; j += i)
prime[j] = false;
}
int main()
{
init();
int L, ans = 0, sum = 0;
scanf("%d", &L);
REP(i, 2, MAXN)
if(prime[i])
{
if(sum + i > L) break;
sum += i;
ans++;
printf("%d\n", i);
}
printf("%d\n", ans);
return 0;
}
复习素数筛
这种写法容易理解,也比较好记
先初始化,然后素数的倍数都不是素数就好了
复杂度nloglogn,非常接近n了
#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int MAXN = 100 + 10;
struct node
{
int a, b;
}k[MAXN];
bool cmp(node x, node y)
{
return x.a < y.a || (x.a == y.a && x.b < y.b);
}
int main()
{
int n;
scanf("%d", &n);
REP(i, 0, n) scanf("%d%d", &k[i].a, &k[i].b);
sort(k, k + n, cmp);
REP(i, 0, n) printf("%d %d\n", k[i].a, k[i].b);
return 0;
}
复习sort,结构体排序
洛谷 P1601 A+B Problem(高精)
#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int MAXN = 1000 + 10;
int a[MAXN], b[MAXN];
char t1[MAXN], t2[MAXN];
int len1, len2, len;
int main()
{
scanf("%s", t1); len1 = strlen(t1);
for(int i = len1 - 1; i >= 0; i--) a[i] = t1[len1-i-1] - '0';
scanf("%s", t2); len2 = strlen(t2);
for(int i = len2 - 1; i >= 0; i--) b[i] = t2[len2-i-1] - '0';
len = max(len1, len2);
REP(i, 0, len)
{
a[i] += b[i];
a[i+1] += a[i] / 10;
a[i] %= 10;
}
if(a[len]) len++;
for(int i = len - 1; i >= 0; i--) printf("%d", a[i]);
return 0;
}
复习高精度加法
一 注意要反过来存入,反过来输出
二 字符串一般输入用scanf
一行带空格的字符要输入才用gets
scanf会在空格终止,而gets会读入空格
洛谷 P1303 A*B Problem 题解
#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int MAXN = 4000 + 10;
int a[MAXN], b[MAXN], c[MAXN];
char t1[MAXN], t2[MAXN];
int len1, len2, len;
int main()
{
scanf("%s", t1); len1 = strlen(t1);
for(int i = len1 - 1; i >= 0; i--) a[i] = t1[len1-i-1] - '0';
scanf("%s", t2); len2 = strlen(t2);
for(int i = len2 - 1; i >= 0; i--) b[i] = t2[len2-i-1] - '0';
REP(i, 0, len1)
REP(j, 0, len2)
c[i+j] += a[i] * b[j];
len = 1;
while(1)
{
c[len] += c[len-1] / 10;
c[len-1] %= 10;
if(c[len]) len++;
else break;
}
for(int i = len - 1; i >= 0; i--) printf("%d", c[i]);
return 0;
}
一 自己测试的时候不但样例要过,还要考虑特殊情况。比如这道题的乘上0,那么答案的位数的情况就不一样了
二 进位问题。边算边进位和最后一起进位结果是一样的。最后答案的位数处理比较细节。最后一次性处理笔记方便。如果边算边进位的话,还要考虑最高位是否为0以及最高位要不要进位
三 模板题可以看例题,别人的代码学习,其他情况下绝对要独立自己写出来
今日的学习部分结束了
一 时间安排上。大一上课不多。周一到周五就是自己把课表填满。晚上做其他事情
二 毕竟两年没碰了,现在慢慢恢复实力。明天在刚一道阶乘的题目,高精度就告一段落了。
还行,就这样一步一步扎扎实实地提升。我不会再像高中那样抄题解麻痹自己,我会认认真真独立完成题目。加油。
9.24 周四
洛谷 P1009 阶乘之和
#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int MAXN = 10000 + 10;
int sum[MAXN], t[MAXN], lens, lent;
void add()
{
lens = max(lens, lent);
REP(i, 0, lens)
{
sum[i] += t[i];
sum[i+1] += sum[i] / 10;
sum[i] %= 10;
}
if(sum[lens]) lens++;
}
void mul(int k)
{
REP(i, 0, lent) t[i] *= k;
REP(i, 0, lent)
{
t[i+1] += t[i] / 10;
t[i] %= 10;
}
while(t[lent])
{
t[lent+1] += t[lent] / 10;
t[lent] %= 10;
lent++;
}
}
int main()
{
int n;
scanf("%d", &n);
lens = lent = 1;
t[0] = 1;
_for(i, 1, n)
{
mul(i);
//printf("i: %d lent: %d\n", i, lent);
//for(int i = lent - 1; i >= 0; i--) printf("%d", t[i]);
//puts("");
add();
}
for(int i = lens - 1; i >= 0; i--) printf("%d", sum[i]);
return 0;
}
一 高精乘低精时,要不用两个数组,要不乘完再一起进位
二 进位时用while,和加法不一样,乘法可能进多位。同时进位时注意+1-1的问题,想清楚
9.25 周五
洛谷 P1255 数楼梯
#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int MAXN = 2000;
int f[5][MAXN], len[5];
void add(int k)
{
int p1 = (k-2) % 3, p2 = (k-1) % 3, p3 = k % 3;
memset(f[p3], 0, sizeof(f[p3]));
len[p3] = max(len[p2], len[p1]);
REP(i, 0, len[p3])
{
f[p3][i] += f[p1][i] + f[p2][i];
f[p3][i+1] += f[p3][i] / 10;
f[p3][i] %= 10;
}
if(f[p3][len[p3]]) len[p3]++;
}
int main()
{
int n;
scanf("%d", &n);
if(n <= 2)
{
printf("%d", n);
return 0;
}
f[1][0] = 1; f[2][0] = 2;
len[1] = len[2] = 1;
_for(i, 3, n) add(i);
for(int i = len[n%3] - 1; i >= 0; i--) printf("%d", f[n%3][i]);
puts("");
return 0;
}
这道题独立做出,还是学到很多的
题目要做精,一要独立做出,二做完后学习别人的做法,吸收优点
虽然时间会长一点,但这样其实效率更高,一道题一道题百分百掌握,踏踏实实积累,实力稳步提升
总结
一 自己尝试极限数据 这里最大推出高精度,最小推出特判。
除了过样例,还要自己尝试极限数据。
数据一大就发现爆了,要高精度。数据小了发现0要特判,不然90
二 二维数组范围
我开始觉得5000 * 5000 空间会超吧。其实我对这个空间认识不清,一般来说3千万,所以5000 * 5000是极限的,空间不会超。
三 高精度的具体实现方法
有些题答案巨大要用到高精度。如果要写重载运算符的话好麻烦,那部分的语法我不懂。
所以要写高精度的时候还是上数组吧。
四 递归复杂度
一开始写的递归,然后超时。其实这个时间复杂度很高的,2的n次方
一般要控制在10的八次方,也就是一个亿左右才不会超时
这个5000肯定超
五 三个数组的mod实现方法 循环数组
这里我为了省空间写了循环数组,核心是用mod 3 处理下标
洛谷 P1002 过河卒
#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int MAXN = 30;
typedef long long ll;
ll f[MAXN][MAXN];
int dir[8][2] = {{1, 2}, {1, -2}, {-1, 2}, {-1, -2}, {2, 1}, {-2, 1}, {2, -1}, {-2, -1}};
int main()
{
int x, y, n, m;
scanf("%d%d%d%d", &n, &m, &x, &y);
f[x][y] = -1;
REP(i, 0, 8)
{
int tx = x + dir[i][0], ty = y + dir[i][1];
if(tx < 0 || tx > n || ty < 0 || ty > m) continue;
f[tx][ty] = -1;
}
if(f[0][0] == -1 || f[n][m] == -1)
{
puts("0");
return 0;
}
f[0][0] = 1;
_for(i, 0, n)
_for(j, 0, m)
if(f[i][j] != -1)
{
if(i == 0 && j == 0) continue;
ll a = i == 0 ? 0 : max(f[i-1][j], ll(0));
ll b = j == 0 ? 0 : max(f[i][j-1], ll(0));
f[i][j] = a + b;
}
printf("%lld\n", f[n][m]);
return 0;
}
一 尽量一次AC
提交前就考虑周全
因为比赛时是不能给你这么清楚的反馈的。
除了过样例,还要自己设一些难一些的数据测试,才能发现问题
比如这里用极限数据发现要用long long
二 答案出现负数大概率是爆int了
这种递推有爆int的可能,自己设数据尝试一下
三 马方向数组
9.26 周六
洛谷 P1028 数的计算
#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int MAXN = 1000 + 10;
int a[MAXN];
int f(int x)
{
if(a[x]) return a[x];
int t = 1;
for(int i = x / 2; i >= 1; i--) t += f(i);
return a[x] = t;
}
int main()
{
int n;
scanf("%d", &n);
a[1] = 1;
_for(i, 2, n) f(i);
printf("%d\n", a[n]);
return 0;
}
一开始写递归,输入1000,发现会超时
这时发现可以记忆化,就记忆化了一下
然后就ac了
这时候我又去看别人怎么做的,发现别人是递推
其实很多时候记忆化搜索就可以写成递推
我写复杂了,写递推更简洁一些。
不过也算复习了一下记忆化搜索
发现前面的状态可以用前面求出的结果表示时,就用递推
洛谷 P1164 小A点菜
#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int MAXN = 1000 + 10;
int a[MAXN], ans, n, m;
void f(int sum, int k)
{
// printf("sum: %d k: %d ans: %d\n", sum, k, ans);
REP(i, k, n)
{
if(a[i] > sum) return;
if(a[i] == sum) ans++;
else f(sum - a[i], i + 1);
}
}
int main()
{
scanf("%d%d", &n, &m);
REP(i, 0, n) scanf("%d", &a[i]);
sort(a, a + n);
f(m, 0);
printf("%d\n", ans);
return 0;
}
dp以后再复习,太久了现在不熟悉,写了搜索
最近题目新加了一组数据把搜索卡了一个点
洛谷 P1464 Function(记忆化搜索)
#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int MAXN = 25;
typedef long long ll;
ll f[MAXN][MAXN][MAXN];
ll w(ll a, ll b, ll c)
{
if(a <= 0 || b <= 0 || c <= 0) return 1;
if(a > 20 || b > 20 || c > 20) return w(20, 20, 20);
if(f[a][b][c]) return f[a][b][c];
if(a < b && b < c) return f[a][b][c] = w(a, b, c-1) + w(a, b-1, c-1) - w(a, b-1, c);
return f[a][b][c] = w(a-1, b, c) + w(a-1, b-1, c) + w(a-1, b, c-1) - w(a-1, b-1, c-1);
}
int main()
{
ll a, b, c;
while(scanf("%lld%lld%lld", &a, &b, &c))
{
if(a == -1 && b == -1 && c == -1) break;
printf("w(%lld, %lld, %lld) = %lld\n", a, b, c, w(a, b, c));
}
return 0;
}
一 不要只满足过样例,过完样例要停一停自己出一些数据,尤其是数据好出的时候。这里一出极限数据发现要用longlong
二 记忆化搜索和动态规划很像,但可以避免一些无用状态,可以剪枝,而动态规划要遍历所有状态
9.27 周日
洛谷 P1044 栈(卡特兰数复习)
#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int MAXN = 20;
int h[MAXN];
int main()
{
h[0] = h[1] = 1;
_for(i, 2, 18)
_for(j, 0, i - 1)
h[i] += h[j] * h[i-j-1];
int n;
scanf("%d", &n);
printf("%d\n", h[n]);
return 0;
}
想了好久,没有突破,不过在这过程中锻炼了思维能力
想到了分成两段,但是我分段的标准是最后一个数
实际上应该是最后一个出栈的数,这样非常巧妙地可以转化为两个已经求过的状态
太巧了
实际上就是卡特兰数,之前我还专门学过,现在两年多过去已经忘记了
实力下降太厉害了
复习卡特兰数,这是一类问题
这个写的非常好
一 数列是 1 1 2 5 14 42…… 如果看到前几项是这个,就可以猜是卡特兰数了
二 出栈问题是经典,写出递推式h(0) = h(1) = 1 h(n) = h(0) * h(n-1) + h(1) * (n-2)……h(n-1) * h(0)
也可以写成 C(2n, n)/ (n + 1)
以后自己推出这个式子就是卡特兰数
三 出栈问题可以有很多变形,本质都是01序列问题
给出一个n,要求一个长度为2n的01序列,使得序列的任意前缀中1的个数不少于0的个数, 有多少个不同的01序列?
洛谷 P2249 【深基13.例1】查找
#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int MAXN = 1e6 + 10;
int a[MAXN];
int main()
{
int n, m;
scanf("%d%d", &n, &m);
_for(i, 1, n) scanf("%d", &a[i]);
while(m--)
{
int q, mid, l = 0, r = n + 1;
scanf("%d", &q);
while(l + 1 < r)
{
mid = (l + r) >> 1;
if(a[mid] >= q) r = mid;
else l = mid;
}
if(a[r] != q) printf("-1 ");
else printf("%d ", r);
}
return 0;
}
开始复习二分
自己以前写的东西对现在的我有非常大的帮助
原来这是写博客的好处
这道题是裸题
把我之前总结的二分稍微改一下就好
脑子里清楚l, r, mid
洛谷 P1102 A-B 数对
#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int MAXN = 2e5 + 10;
int a[MAXN], n, c;
long long ans;
int f(int p)
{
int key, l, r;
key = a[p] + c;
l = lower_bound(a + p + 1, a + n + 1, key) - a;
r = upper_bound(a + p + 1, a + n + 1, key) - a;
return r - l;
}
int main()
{
scanf("%d%d", &n, &c);
_for(i, 1, n) scanf("%d", &a[i]);
sort(a + 1, a + n + 1);
_for(i, 1, n) ans += f(i);
printf("%lld\n", ans);
return 0;
}
一 二分可以用lower_bound和upper_bound,很方便,不用手写
二 我一开始一直有一个点过不了。后来发现是ans要开long long
我为数据范围还是不太敏感
这里其实可以有很多很多种数对。因此ans要开long long
洛谷 P1873 砍树
#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
typedef long long ll;
const int MAXN = 1e6 + 10;
int h[MAXN], n, m;
bool check(int k)
{
ll sum = 0;
_for(i, 1, n) sum += max(h[i] - k, 0);
return sum >= m;
}
int main()
{
int l = 0, r = 0, mid;
scanf("%d%d", &n, &m);
_for(i, 1, n)
{
scanf("%d", &h[i]);
r = max(r, h[i]);
}
while(l + 1 < r)
{
mid = (l + r) >> 1;
if(!check(mid)) r = mid;
else l = mid;
}
printf("%d\n", l);
return 0;
}
二分答案模板题
今天量挺大的
最后一个小时左右效率变低了,脑子有点不想动了
那今天就这样吧
明天弄弄 解方程,两道二分答案题,二分就过了