愿意的可以跟我一起刷,每个类型做1~5题 ,4月前还可以回来系统复习
2月13日 ~ 3月28日,一共32天
一个月时间,0基础省三 --> 省二;基础好点的,省二 --> 省一
目录
前言
一,前缀和
(一)3956. 截断数组
Time Limit Exceeded
Accpted
(二)[NewOJ Week 1] 前缀和的因子数
(三)P4702 取石子
(四)P3056 [USACO12NOV]Clumsy Cows S
栈 AC 12%
栈 AC 75%
栈 AC 100%
前缀和 AC 100%
(五)P1147 连续自然数和
AC 56%
AC 100%
二,差分
(一)AcWing 3729. 改变数组元素
(二)P2367 语文成绩
(三)P8772 [蓝桥杯 2022 省 A] 求和
三,二分
(一)1460. 我在哪?
(二)1101: 花费(未理解)
四,双指针(尺取法)
(一) 3768. 字符串删减
(二) 2008: 三数之和
AC 50% 暴力
AC 100%
(三)1373: [蓝桥杯2018初赛]日志统计
AC 38%(大坑!)
AC 100%
五,递推
(一)3777. 砖块
(二) 1428: [蓝桥杯]翻硬币
六,递归
(一) 1497. 树的遍历
Wrong Answer
Accpted 加个if
Accpted map迭代器
(二) P1427 小鱼的数字游戏
(三)P3152 正整数序列
(四)1103: 地盘划分
(五)1104: 分形图
七,
八
九,
十,
十一,
十二,
十三,
十四,
十五,
十六,
十七,
十八,
十九,
二十,
二十一,
二十二,
二十三,
二十四,
二十五,
二十六,
二十七,
二十八,
二十九,
三十,
总结
前缀和,差分,二分,双指针,递归,递推,BFS,DFS,Dijkstra, Floyd,质数筛,最大公约数,背包dp,线性dp,区间dp,组合计数,快速幂,KMP,哈希,并查集,博弈论
每个类型第一题来自AcWing蓝桥杯集训-每日一题
1,花5块钱
2,上洛谷找替代 / 原题
题型有
前缀和,差分,二分,双指针,递推,递归,并查集,哈希,单调队列,
KMP,Trie,BFS,DFS,拓扑排序,Dijkstra,Floyd,最小生成树,
最近公共祖先,二分图,筛质数,最大公约数,快速幂,组合计数,博弈论,
背包DP,线性DP,区间DP,树型DP,树状数组,线段树,矩阵乘法
如果你想冲省一,拿22年A组为例,你得拿60分,也就是2道填空题 + 4道编程题
5 + 5 + 10 + 10 + 15 + 15
省赛需要掌握的有:
前缀和,差分,二分,双指针,递归,递推,BFS,DFS,Dijkstra, Floyd,质数筛,最大公约数,背包dp,线性dp,区间dp,组合计数,快速幂,KMP,哈希,并查集,博弈论
围绕省赛需要掌握的类型,针对性地下手
先给大家看个时间复杂度(来源于AcWing)
多年后再见你 - 乔洋/周林枫 - 单曲 - 网易云音乐
→ 前缀和 & 差分 - OI Wiki (oi-wiki.org)
3956. 截断数组 - AcWing题库
类型:枚举,前缀和,中等
还有个坑,虽然10^5 * 10^4 < int,但是由于存在1e5个0的情况,这时答案约等于1e10,就会爆int,所以ans还是开long long
按着思路枚举,将数组分为3部分,中间部分左边界i游标,右边界j游标
然后在过了所有样例的基础上,再想2组测试,可以就提交
两组测试
8
7 2 3 2 1 3 10 14
1
7
3 -2 1 5 -3 4 -2
1
#include
#include //scanf()
using namespace std;
int a[100010], n;
bool check(int i, int j) //i左游标, j右游标
{
int sum1 = 0, sum2 = 0, sum3 = 0;
for(int m = 0; m < n; ++m) {
if(m < i)
sum1 += a[m];
else if(m >= i && m <= j)
sum2 += a[m];
else
sum3 += a[m];
}
if(sum1 == sum2 && sum2 == sum3)
return true;
else
return false;
}
int main()
{
int i, j;
scanf("%d", &n);
for(i = 0; i < n; ++i)
scanf("%d", &a[i]); //读入数据
int ans = 0; //截断方法数量
for(i = 1; i < n - 1; ++i) //枚举中间部分
for(j = i; j < n - 1; ++j) {
if(check(i, j)) ans++;
}
cout<
当n = 100000,直接暴力会超时,下面是优化
第一次接触前缀和,本题注意几个点
1,ans开long long
2,三部分都非空
3,数据达到1e5,所以复杂度O(n^2)暴力枚举不行,至少得O(nlogn)
4,代码第17,18行,s[i]表示第一部分前缀和,s[i + 1]表示第二部分前缀和
补充一组测试
8
1 0 1 0 0 0 0 1
10
#include
#include //scanf()
using namespace std;
int s[100010];
int main()
{
int n;
scanf("%d", &n);
for(int i = 1; i <= n; ++i) { //从1开始读入
scanf("%d", &s[i]);
s[i] += s[i - 1]; //前缀和, s[i]表示前i项的和
}
long long cot = 0, ans = 0; //截断方法数量
if(s[n] % 3 != 0) cout<<0;
else {
for(int i = 1; i <= n - 2; ++i) { //整个数组下标从1到n
if(s[i] == s[n] / 3) cot++; //
if(s[i + 1] == s[n] / 3 * 2) ans += cot; //
}
cout<
解释:cot先自增,ans再加上cot,cot表示第一部分满足三等分的数量,ans表示既满足第一部分三等分,又满足第三部分三等分的情况
P1791 - [NewOJ Week 1] 前缀和的因子数 - New Online Judge (ecustacm.cn)
分类:基础题,欧拉,数论,前缀和
细心是关键
1,前缀和怎么求要会
2,因数怎么求要会
3,i, j游标别搞反了
AC 代码
#include
#include //scanf()
using namespace std;
int s[100010];
int main()
{
int n;
scanf("%d", &n);
for(int i = 1;; ++i) {
s[i] = i;
s[i] += s[i - 1]; //前缀和
int sum = 0;
for(int j = 1; j * j <= s[i]; ++j) {
if(s[i] % j == 0 && j * j != s[i])
sum += 2; //求因数
else if(s[i] % j == 0 && j * j == s[i])
sum++;
}
if(sum > n) {
cout<
P4702 取石子 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
类型:前缀和,入门
一看,感觉好难,为什么才入门。。看了眼题解。。
确实入门,纯前缀和,只是前面加了个类似贪心的判断
→想要分出输赢,石子必须取完
为什么呢?因为“a0视为0”,所以。。显而易见
AC 代码
#include
#include //scanf()
using namespace std;
int s[100010];
int main()
{
int n;
scanf("%d", &n);
for(int i = 1; i <= n; ++i) {
scanf("%d", &s[i]);
s[i] += s[i - 1];
}
if(s[n] % 2 == 0) cout<<"Bob";
else cout<<"Alice";
return 0;
}
P3056 [USACO12NOV]Clumsy Cows S - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
大概意思就是,输入偶数长度的括号,最少修改多少次,使左右匹配,也就是都朝里
第一次我用栈做(4条消息) C++ STL 之stack_buyizhu021的博客-CSDN博客_stack赋值
#include
#include //st.push(), st.pop(), st.top()
using namespace std;
string s;
int main()
{
stackst;
cin>>s;
for(int i = 0; i < s.size(); ++i) {
if(st.size() == 0)
st.push(s[i]); //元素不足2个
else if(st.size() >= 1) {
if(st.top() == '(' && s[i] == ')') {
st.pop(); //如果栈顶元素和新的元素匹配
}
else
st.push(s[i]); //不匹配就把新的元素入栈
}
}
cout<
原来是忘记对结果分类讨论了
如果最后剩下 ))(( 或者 ((((,都需要2次;而剩下 )((( 需要3次
#include
#include //st.push(), st.pop(), st.top()
using namespace std;
string s;
int main()
{
stackst;
cin>>s;
for(int i = 0; i < s.size(); ++i) {
if(st.size() == 0)
st.push(s[i]); //元素不足2个
else if(st.size() >= 1) {
if(st.top() == '(' && s[i] == ')') {
st.pop(); //如果栈顶元素和新的元素匹配
}
else
st.push(s[i]); //不匹配就把新的元素入栈
}
}
//分类讨论
int a = 0, b = 0;
for(int i = 0; i
找到问题了,第23,24行,应该用st[i]代替s[i],无奈栈没有这种用法,应该改用st.top(), st.pop()来操作
#include
#include //st.push(), st.pop(), st.top()
using namespace std;
string s;
int main()
{
stackst;
cin>>s;
for(int i = 0; i < s.size(); ++i) {
if(st.size() == 0)
st.push(s[i]); //元素不足2个
else if(st.size() >= 1) {
if(st.top() == '(' && s[i] == ')') {
st.pop(); //如果栈顶元素和新的元素匹配
}
else
st.push(s[i]); //不匹配就把新的元素入栈
}
}
//分类讨论
int a = 0, b = 0;
while(!st.empty()) {
if(st.top() == ')') {
a++;
st.pop();
}
else if(st.top() == '(') {
b++;
st.pop();
}
}
int c;
if(a % 2 == 1) c = 2 + (a + b - 2) / 2;
else c = (a + b) / 2;
cout<
常犯错误了,栈就要用栈的表达,不要老想着什么st[i],日后多多复盘
#include
int main()
{
stackst;
st.push();
st.top();
st.pop();
if(!st.empty())
......
return 0;
}
说实话,怎么用前缀和我没想清楚,不过你可以用))((, ((((, ))))三个例子去模拟以下就知道了
#include
using namespace std;
string s;
int main()
{
cin>>s;
int m = 0, n = 0;
for(int i = 0; i < s.size(); ++i) {
if(s[i] == '(') m++;
else m--;
if(m == -1) {
n++;
m = 1;
}
}
cout<
P1147 连续自然数和 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
标签:普及-,前缀和,双指针
真的就前缀和 + 双指针
最后三个样例TLE(Time Limit Exceeded)超时了
想法是,把第8,9行代码去掉,放到第二部分里算前缀和,这样就少一个O(n)的复杂度
毕竟题目是2e6的数据?
#include
using namespace std;
int s[10010];
int main()
{
int n, i, j;
cin>>n;
for(i = 1; i <= 10010; ++i)
s[i] = i + s[i - 1]; //前缀和
for(i = 0, j = 2; i <= n / 2;) {
if(s[j] - s[i] == n) {
cout< n) //尺取法
i++; //左指针
if(s[j] - s[i] < n)
j++; //右指针
}
return 0;
}
我们用sum代替前缀和的差值,注意 sum += j 和 sum -= i 与 j++ 和 i++ 的相对位置关系
#include
using namespace std;
int s[10010];
int main()
{
int n, i, j, sum = 3;
cin>>n;
for(i = 0, j = 2; i <= n / 2;) {
if(sum == n) {
cout< n) {//尺取法
sum -= i;
i++; //左指针
}
if(sum < n) {
j++; //右指针
sum += j;
}
}
return 0;
}
自在的少年 - 要不要买菜 - 单曲 - 网易云音乐
→ 差分算法介绍_木木夕夕山的博客-CSDN博客_差分算法
→ (1条消息) 算法笔记(六):差分法_G鸦青的博客-CSDN博客_差分法
→ 前缀和 & 差分 - OI Wiki (oi-wiki.org)
→ 前缀和、二维前缀和与差分的模板小总结 - AcWing
概以括之
差分是前缀和的逆运算,假设有两个数组a[], b[],数组a是数组b的前缀和
即a[i] = b[1] + b[2] + ... + b[i],那么数组b就是a的差分数组
差分算法有什么用呢,比如给定数组a有n个元素,要求在[left, right]上,每个元素 +c,这时你可能会想,遍历一次,加c不就好了,可是这样时间复杂度达到O(n),而采用差分只有O(1)的复杂度
如果进行m次区间[left, right] +c 或 -c 的操作呢,这时遍历m次的复杂度为O(nm),而差分只需O(m)
在数据量达到1e5的情况下,直接遍历会TLE,对于OI赛制只能拿到10%~50%的分,ACM赛制0分
所以学习差分还是有必要的
具体操作
给定数组a,n个元素,我们创建数组b,为数组a的差分数组
令b[i] = a[i] - a[i - 1],因为a是b的前缀和数组
模板
#include
using namespace std;
int n;
int a[10010], b[10010];
//插入函数
void insert(int l, int r, int c)
{
b[l] += c;
b[r + 1] -= c;
}
int main()
{
cin>>n;
//输入数据
for(int i = 1; i <= n; ++i)
cin>>a[i];
//差分后的数组b
for(int i = 1; i <= n; ++i)
insert(i, i, a[i]);
//执行操作
int l, r, c;
cin>>l>>r>>c;
insert(l, r, c);
//输出操作后数组
for(int i = 1; i <= n; ++i) {
b[i] += b[i - 1]; //前缀和
cout<
执行m次只需在“执行操作”那里,加个while(m--),不过我更习惯while(m){ ......m--; }
8
1 2 3 4 5 6 7 8
2 5 3
1 5 6 7 8 6 7 8
先记套路,你可以假设1 3 5 7的数组a,经过insert()函数后,得到的数组b就是1 2 2 2,数组b的前缀和就是原来的数组a
3729. 改变数组元素 - AcWing题库
标签:中等,差分,区间合并
题目没给初始数组,所以我们只需要差分数组b
第二是,我们假定初始长度就为n,用一个for(int i = 1; i <= n; ++i)循环遍历1~n
如果输入的x大于等于当前的长度 i ,就对所有元素执行insert操作
如果输入的x小于 i ,只需要insert(i - x + 1, i),i 表示最后一个元素
#include
#include //scanf(), printf()
#include //memset()
using namespace std;
int b[200010]; //题目中没有给定初始数组,所以不需要数组a
void insert(int l, int r)
{
b[l] += 1;
b[r + 1] -= 1; //套路
}
int main()
{
int t, n;
scanf("%d", &t);
while(t) {
scanf("%d", &n);
memset(b, 0, sizeof(b)); //初始化
//执行操作
for(int i = 1; i <= n; ++i) {
int x;
scanf("%d", &x); //每输入一个数, 就操作一次
if(x == 0) continue; //跳过本次
else if(x >= i) insert(1, i); //全部操作一次
else insert(i + 1 - x, i);
}
for(int i = 1; i <= n; ++i)
b[i] += b[i - 1]; //前缀和得到操作后的数组
for(int i = 1; i <= n; ++i) {
if(b[i] <= 1) printf("%d ", b[i]);
else printf("1 "); //如果大于1就输出1
}
printf("\n");
t--;
}
return 0;
}
P2367 语文成绩 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
标签:普及/提高-
这是一道最简单模板题,不需要变形,直接套模板就能AC
不开O2优化就AC 80%,开了AC 100%
能直接套模板的题,基本一次过,而且耗时才几分钟
#include
#include //scanf(), printf()
using namespace std;
int a[5000010], b[1000010];
void insert(int l, int r, int c)
{
b[l] += c;
b[r + 1] -= c; //模板
}
int main()
{
int n, p;
scanf("%d%d", &n, &p);
for(int i = 1; i <= n; ++i)
scanf("%d", &a[i]); //初始分数
for(int i = 1; i <= n; ++i)
insert(i, i, a[i]); //逆前缀和
while(p) { //p次操作
int l, r, c;
scanf("%d%d%d", &l, &r, &c);
insert(l, r, c);
p--;
}
for(int i = 1; i <= n; ++i)
b[i] += b[i - 1]; //前缀和
int Min = 1e9;
for(int i = 1; i <= n; ++i)
Min = min(Min, b[i]); //找最小值
printf("%d", Min);
return 0;
}
P8772 [蓝桥杯 2022 省 A] 求和 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
标签:普及-,前缀和,差分
虽然标签有差分,但我觉得,它只考了前缀和
temp = 数组a[i]的前缀和,然后用a[i] * temp。暴力的话只能AC 70%
temp = a[1], sum += a[2] * a[1]
temp = a[1] + a[2], sum += a[3] * (a[1] + a[2])
temp = a[1] + a[2] + a[3], sum += a[4] * (a[1] + a[2] + a[3])
......
#include
using namespace std;
int a[200010];
int main()
{
int n;
cin>>n;
for(int i = 1; i <= n; ++i)
cin>>a[i];
long long sum = 0, temp = a[1];
for(int i = 2; i <= n; ++i) {
sum += temp * a[i];
temp += a[i];
}
cout<
活着 - 郝云 - 单曲 - 网易云音乐
→ 二分算法学习_码龄?天的博客-CSDN博客
→ OI Wiki - OI Wiki (oi-wiki.org)
模板
int binary_search(int left, int right, int key)
{
int ret = -1; //未搜索到数据返回-1下标
int mid;
while(left <= right) {
mid = left + ((right - left) >> 1); //避免溢出,用该算法
if(key > a[mid]) left = mid + 1;
else if(key < a[mid]) right = mid - 1;
else { //最后检测相等
ret = mid;
break;
}
}
return ret; //单一出口
}
要用二分算法,首先判断
1,能不能用, 即是不是一个有序数组,这里的“有序”是广义的有序,也就是一个数组中,某个点左侧或右侧都满足某一条件,而另一侧不满足
2,其次判断,左右边界是什么,就是left(l), right(r)要自己设或者求出合适的值
3,check()函数怎么写,相当于模板里的key > a[mid]或者 <
1460. 我在哪? - AcWing题库
标签:简单,二分,哈希
原文是“最小的K值,使任意连续K个,唯一”,比如样例ABCDABC中,有人问为什么CD确定不了2呢,因为任意2个里面,包含AB,而AB的位置显然不是唯一的,3个的话也存在ABC重复,所以只能4个
方法一:二分 + 哈希 ,但是我不会哈希,想了半小时只用二分没结果
方法二:利用set中,元素不重复的特点,但是没想明白怎么做
方法三:考虑到数据量不大,还是暴力 + s.substr() + s.find()
(9条消息) C++中s.find()和s.rfind()的用法_暖风有凉意的博客-CSDN博客_s.find
1,s.find(str)或s.find(str, pos)要与 != string::npos连用,最后没有找到子串的话,会返回
string::npos
2,s.substr(i)从下标 i 开始到结尾,s.substr(i, j)从下标 i 开始截取 j 个字符
string s1 = s.substr(j, i);
if(s.find(s1, j + 1) == string::npos)
//没有找到子串, 返回string::npos
if(s.find(s1, j + 1) != string::npos)
//找到子串
#include
#include //s.substr(), s.find()
using namespace std;
int main()
{
int n;
cin>>n;
string s;
cin>>s;
for(int i = 1; i <= n; ++i) { //i表示截取长度
int flag = 1;
for(int j = 0; j < n - i; ++j) { //j为截取起始坐标
string s1 = s.substr(j, i); //下标j开始截取i个字符
if(s.find(s1, j + 1) != string::npos) //j + 1
flag = 0;
}
if(flag) {
cout<
P1101 - 花费 - New Online Judge (ecustacm.cn)
标签:基础题,二分
这道题还挺难的。。。做了3个小时。。。 还是没做出来
只好模仿答案写一遍,不是很理解
本题代码中,主函数中二分查找的复杂度是O(logn),check()函数里的复杂度是O(n)
所以本题总的复杂度是O(nlogn)
#include
#include //scanf(), printf()
typedef long long LL;
using namespace std;
LL n, m, l = 0, r = 0;
LL a[100010];
bool check(LL mid)
{
LL sum = 0, c = 1;
for(LL i = 0; i < n; ++i) {
sum += a[i];
if(sum > mid) {
sum = a[i];
c++;
}
}
return c <= m;
}
int main()
{
scanf("%lld%lld", &n, &m);
for(LL i = 0; i < n; ++i) {
scanf("%lld", &a[i]);
l = max(l, a[i]); //左边界
r += a[i]; //右边界
}
while(l < r) { //二分
LL mid = l + ((r - l) >> 1); //外面记得加上括号
if(!check(mid))
l = mid + 1;
else
r = mid;
}
cout<
就是我 - 林俊杰 - 单曲 - 网易云音乐
歌词:躺在星空下的草地上,衣服全部脱光让你看
→ 算法基础----尺取法(双指针)_jkaliang的博客-CSDN博客
→ 尺取法(图文解析、初学推荐)_小白小郑的博客-CSDN博客_尺取法
→ 双指针 - OI Wiki (oi-wiki.org)
3768. 字符串删减 - AcWing题库
这题用不上双指针
#include
using namespace std;
int main()
{
int n;
cin>>n;
string s;
cin>>s;
int ans = 0, flag = 0, now = 0;
for(int i = 0; i < n; ++i) {
if(s[i] == 'x') now++;
else if(s[i] != 'x' && now >= 3) {
ans += now - 2;
now = 0;
continue;
}
else
now = 0;
}
if(now >= 3) ans += now - 2; //补充最后来不及加上的
cout<
P2008 - 三数之和 - New Online Judge (ecustacm.cn)
分类:双指针,枚举
来暴力枚举
#include
#include //sort()
using namespace std;
int a[5010];
int main()
{
int n, m, i, j, k;
cin>>n>>m;
for(i = 0; i < n; ++i)
cin>>a[i];
sort(a, a + n); //排序
int ans = 0;
for(i = 0, j = 1; i < j; ++i)
{
j = i + 1; //这步很重要
while(j < n - 1) {
for(k = j + 1; k < n; ++k)
if(a[i] + a[j] + a[k] == m)
ans++;
j++;
}
}
cout<
下面这个是别人的答案
1,不排序,采用了桶的概念
2,最后的ans要开long long
#include
using namespace std;
int a[5010], b[10010]; //b类似桶排序
int main()
{
int n, m, i, j;
cin>>n>>m;
for(i = 1; i <= n; ++i) {
cin>>a[i];
b[a[i] + 2000]++; //防止下标 < 0
}
//不要排序
long long ans = 0; //会爆int
for(i = 1; i <= n; ++i) {
for(j = i + 1; j <= n; ++j) {
int tmp = m - a[i] - a[j];
int now = b[2000 + tmp];
if(a[i] == tmp) now--;
if(a[j] == tmp) now--;
if(now > 0) ans += now;
}
}
cout<
P1373 - [蓝桥杯2018初赛]日志统计 - New Online Judge (ecustacm.cn)
标签:基础题,双指针
#include
#include //sort()
#include //scanf()
using namespace std;
int flag[100010], num[100010]; //标记热帖和点赞数
struct good
{
int ts, id;
};
int cmp(good x, good y)
{
return x.ts < y.ts; //时间从小到大排序
}
int main()
{
struct good a[100010];
int n, d, k;
scanf("%d%d%d", &n, &d, &k);
for(int i = 0; i < n; ++i)
scanf("%d%d", &a[i].ts, &a[i].id);
sort(a, a + n, cmp); //排序
for(int i = 0, j = 0; i < n; ++i) {
num[a[i].id]++; //点赞数+1
if(a[i].ts - a[j].ts >= d) {//时间达到d
num[a[j].id]--; //取消左边界获赞
j++; //左边界右移
}
if(num[a[i].id] == k) //这里是a[i]
flag[a[i].id] = 1; //标记热帖
}
for(int i = 0; i < n; ++i)
if(flag[i] == 1) //被标记过
cout<
对拍错误样例
10 100 2
0 4
99 4
5 17
102 17
6 5
9 6
10 11
20 15
25 20
30 30
4
少输出了个17
忘记对拍了,,白白浪费2小时找原因,对拍1分钟找到错误原因
不是没用pair的原因,。。。代码最后输出时,第31行
for(int i = 0; i < n; ++i)改成for(int i = 0; i <= 100000; ++i)即可,原来输出少了。。。
n只是日志行数,不是帖子总数。。。。
输出少了,看了3小时才看出来 粗心是时间杀手
(3条消息) C++ pair用法及使用sort函数对pair数据进行排序_一记绝尘的博客-CSDN博客_c++ pair排序
#include
using namespace std;
const int N = 1e5 + 10;
int flag[N]; //标记热帖
int num[N]; //点赞数
struct good
{
int ts, id;
};
int cmp(good x, good y)
{
return x.ts < y.ts; //时间从小到大排序
}
int main()
{
struct good a[N];
int n, d, k;
cin >> n >> d >> k;
for (int i = 0; i < n; i++)
cin >> a[i].ts >> a[i].id;
sort(a, a + n, cmp);
for (int i = 0, j = 0; i < n; i++)
{
num[a[i].id]++;
if (a[i].ts - a[j].ts >= d)
num[a[j].id]--, j++;
if (num[a[i].id] == k)
flag[a[i].id] = true;
}
for (int i = 0; i < N; i++)
if (flag[i] == 1)
cout << i << endl;
return 0;
}
菲菲 - 沈以诚 - 单曲 - 网易云音乐
→ c++ 递推算法 - 张其勋 - 博客园 (cnblogs.com)
一种做法是找到递推式,类似f(n) = f(n - 1) + f(n - 2)这种
第二种是,通过递推的思路做题,但不用找到递推式
3777. 砖块 - AcWing题库
→ (2条消息) c++ vector详解_~不羁的博客-CSDN博客
vector能代替数组,而且能操作更大的数据量,比如1e7的数据量哪怕放全局数组也会超限
但是放vector可以
发现stl很多操作,和python很像,还是得赶紧掌握stl,以后做题难度直线下降
补充: s.back()表示字符串s末尾得字符
两种情况都要讨论到,就是全转化为'B'或全转化为'W'
代码中的check(char& c)我一开始写成check(char c)
结果没有达到预期效果
→ C与C++关于*与&的传参解析 (baidu.com)
AC 代码
#include
#include //res.push_back
using namespace std;
int n;
void change(char& c) //&
{
if(c == 'W') c = 'B';
else c = 'W'; //修改
}
bool check(string s, char c)
{
vectorres; //result, 局部变量, 不影响函数外
for(int i = 0; i + 1 < n; ++i)
if(s[i] != c) {
res.push_back(i + 1); //读入操作位置
change(s[i]);
change(s[i + 1]); //一次改2个
}
if(s[0] != s[n - 1]) return false;
cout<>t;
while(t) {
cin>>n>>s;
//有一个真则不执行另一个
if(!check(s, 'B') && !check(s, 'W'))
cout<<-1<
4
8
BWWWWWWB
3
2 4 6
4
BWBB
-1
5
WWWWW
0
3
BWB
2
1 2
P1428 - [蓝桥杯]翻硬币 - New Online Judge (ecustacm.cn)
标签:基础题,递推
#include
#include
using namespace std;
int main()
{
string s1, s2, s3;
cin>>s1>>s2;
cout<
ACm Oi
s2的值赋给s1
Oi Oi
+号拼接s1, s2得到s3
OiOi
初始化s4为指定元素
AAA
判等
s1 == s2
s3 != s4
s4追加到s3后
OiOiAAA
下标1开始截取s3的4个字符
iOiA
输出s3第3 + 1个字符
i
删除s3下标2开始的3个字符
OiAA
嗯。。第一次花了一个小时敲出来,只过了样例。。第二次在网上搜题解
突然发现这题没有递推把??就是个贪心
怎么贪心法呢,比如有x1, x2, x3, x4,四个位置的字符不同
1,按照顺序翻转,次数t1 = x2 - x1 + x4 - x3
2, 两边凑一对翻转,次数t2 = x4 - x1 + x3 - x2
显然t1 < t2,所以按顺序翻转可以得到最小的翻转次数
而且首先可以明确的是,两字符串不一样的字符一定是偶数个,所以不用纠结万一不能翻转成一样怎么办
AC 代码
#include
using namespace std;
int main()
{
string s1, s2;
cin>>s1>>s2;
int ans = 0, flag = 0, m; //flag = 0放循环外
for(int i = 0; i < s1.size(); ++i) {
if(s1[i] != s2[i]) {
if(flag == 1) { //按顺序第2个不同的字符
flag = 0;
ans += i - m;
}
else if(flag == 0) { //按顺序第1个不同的字符
flag = 1; //第15行记得用else if
m = i;
}
}
}
cout<
▶ 小宇 (163.com)
→ 关于递归中return的理解(最浅显易懂)_Pledgee的博客-CSDN博客_递归函数return怎么理解
→ 三道题套路解决递归问题 | lyl's blog (lyl0724.github.io)
→ 递归 & 分治 - OI Wiki (oi-wiki.org)
1497. 树的遍历 - AcWing题库
标签:简单?,树的遍历,递归,哈希表,PAT甲级,BFS
说实话,难到我了。。哈希还没学,树的四种遍历顺序(前序,中序,后续,层序)也没学。。BFS也不是很熟练,先滚去学习了
先开2倍速看完y总的视频,了解到,
还需要学习:1,哈希表,2,树的前序中序后续层序遍历(二叉树)
还需要巩固:1,BFS
建议都看一遍,取精去糟
树的四种遍历
→
【二叉树初阶】前中后序遍历+层序遍历+基础习题_二叉树的先序,中序,后序遍历例题_寄一片海给你的博客-CSDN博客
→ 二叉树的四种遍历方式_孤单网愈云的博客-CSDN博客_本题要求给定二叉树的4种遍历。
→ 二叉树的四种遍历方式 - 洛沐辰 - 博客园 (cnblogs.com)
→ 树的遍历(四种全)|C++_世一渔的博客-CSDN博客_树的遍历
→ 树基础 - OI Wiki (oi-wiki.org)
→
二叉树:前序遍历、中序遍历、后序遍历,BFS,DFS - 蔡子CaiZi - 博客园 (cnblogs.com)
哈希表 + BFS
哈希表是一种数据结构,内部通过哈希算法实现
→ 《啊哈算法第四章之bfs》(17张图解)_码龄?天的博客-CSDN博客
→ 哈希算法原理和实现_非常规自我实现的博客-CSDN博客_hash原理和实现方式
→ 哈希算法详解_qq_16570607的博客-CSDN博客_哈希算法
→ 来吧!一文彻底搞定哈希表!_庆哥Java的博客-CSDN博客_哈希表怎么画
→ (八):哈希表+二叉树_bupt_01的博客-CSDN博客_哈希表和二叉树
→ 字符串哈希 - OI Wiki (oi-wiki.org)
→ 哈希表 - OI Wiki (oi-wiki.org)
本题考察的点较多,属于面试热门考点,思路是:
1,利用给出的后续中序遍历,构造出二叉树(其中需要哈希表存储树)
2,用BFS按层遍历二叉树
嗯,二叉树遍历的文章看完了,可是看完了也不会啊,哈希表懒得看了
所幸这时从网上发现了一种不需要哈希和bfs的方法
看这里!
先讲讲根据后序,中序得到前序的方法,这是推出层序的基础
1,根据后序得到根节点,然后在中序里找到根节点,根节点左边就是左子树,右边右子树
(后序最后一个就是根节点)
2,写一个pre推出前序的函数,函数类似快排,找到根节点后,分别对左子树和右子树递归
快排长这样子
void quick_sort(int left, int right, int a[])
{
if(left >= right) return;
int i, j, base;
i = left, j = right, base = a[left];
while(i < j) {
while(i < j && a[j] >= base) j--;
while(i < j && a[i] <= base) i++;
if(i < j) {
a[i] = a[i]^a[j];
a[j] = a[i]^a[j];
a[i] = a[i]^a[j]; //异或交换两数
}
}
a[left] = a[j]; //左端与i,j指向元素交换
a[j] = base;
quick_sort(left, j - 1, a); //递归左边
quick_sort(j + 1, right, a); //递归右边
}
推出前序的长这个样子
#include
using namespace std;
int in[] = {3,2,4,1,6,5}; //中序
int post[] = {3,4,2,6,5,1}; //后序
void pre(int root, int start, int End)
{
if(start > End) return;
int i = start;
while(i < End && in[i] != post[root]) i++;
cout<
return; 和 左右递归那里,是不是很像
具体解释下第11,12行pre()函数对左右子树的递归
int in[] = {3,2,4,1,6,5}; //中序
int post[] = {3,4,2,6,5,1}; //后序
结合代码和图好理解
首先明确,这里下标从0开始(如果从1开始会有所变化)
1,第11行
(1)左子树根节点在后序中的下标为root - (End - i) - 1
root 为当前部分节点数(不算根节点)
(End - i) 为当前部分右子树的节点数
所以节点数 - 右子树部分 = 左子树节点数,因为是算下标,所以再 - 1
(2)左子树在中序中的起点为start,终点为 i - 1
2,第12行
(1)右子树根节点在后序中的下标为 root - 1
(2)右子树在中序中的起点为 i + 1,终点为 End
前序说到这里,那怎么在这个过程中,推出层序呢?
首先,编号(下标)从0开始的话,根节点为k,那么左子树根节点为2*k + 1,右子树根节点为2*k + 2,我们建个一维数组m,在pre中增加个index作为下标即可,最后按顺序输出自然就是层序
参考博客:
已知后序与中序输出前序(先序) – 柳婼 の blog (liuchuo.net)
AcWing 1497. 树的遍历(无须建树,简单易懂) - AcWing
emm...第一次把代码敲出来,样例没过,因为题目给的是二叉树,不一定是完全二叉树,如果题目说明给的是完全二叉树(或满二叉树),我的代码就对了
#include //scanf(), printf()
int m[100], in[100], post[100];
void pre(int root, int start, int End, int index)
{
if(start > End) return;
int i = start;
while(i < End && in[i] != post[root]) //这里是while
i++; //自增到根节点的下标
m[index] = post[root]; //保存前序的值
//递归左子树
pre(root - (End - i) - 1, start, i - 1, 2*index + 1);
//递归右子树
pre(root - 1, i + 1, End, 2*index + 2);
}
int main()
{
int n;
scanf("%d", &n);
for(int i = 0; i < n; ++i)
scanf("%d", &post[i]); //后序
for(int i = 0; i < n; ++i)
scanf("%d", &in[i]); //中序
pre(n - 1, 0, n - 1, 0);
for(int i = 0; i < n; ++i)
printf("%d ", m[i]); //输出层序
}
比如,给来一个完全二叉树,√
6
3 4 2 6 5 1
3 2 4 1 6 5
1 2 5 3 4 6
给来一个更普遍的二叉树,×
7
2 3 1 5 7 6 4
1 2 3 4 5 6 7
4 1 6 0 3 5 7
那怎么办呢,这就需要map的迭代器了,让我们学习新知识
map会按键从小到大自动排序
C++ map的常用用法(超详细)(*^ー^)人(^ー^*)_c++ map使用_Curz酥的博客-CSDN博客
关于为什么说不是完全二叉树就不行,可以看看这个图
比如我们将Wrong Answer中最后输出到 i < n改为 i < 2*n,就会出现下面的情况
7
2 3 1 5 7 6 4
1 2 3 4 5 6 7
4 1 6 0 3 5 7 0 0 2 0 0 0 0
想明白为什么不是完全二叉树就不行,我们可以加个if(m[i] != 0)的判断,当然,这里你要取到
1.1 * 10^6,否则哪怕取到1e6也是满足不了最后一组数据的,因为最后一组数据就是一条链
20
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1
20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1
当然,1.1 * 10^6我试了很多次,,1.11*10^6的全局都超限了,所以会迭代器的话,还是用迭代器把
#include //scanf(), printf()
int m[1100000], in[1100000], post[1100000];
void pre(int root, int start, int End, int index)
{
if(start > End) return;
int i = start;
while(i < End && in[i] != post[root]) //这里是while
i++; //自增到根节点的下标
m[index] = post[root]; //保存前序的值
//递归左子树
pre(root - (End - i) - 1, start, i - 1, 2*index + 1);
//递归右子树
pre(root - 1, i + 1, End, 2*index + 2);
}
int main()
{
int n;
scanf("%d", &n);
for(int i = 0; i < n; ++i)
scanf("%d", &post[i]); //后序
for(int i = 0; i < n; ++i)
scanf("%d", &in[i]); //中序
pre(n - 1, 0, n - 1, 0);
for(int i = 0; i < 1100000; ++i)
if(m[i] != 0)
printf("%d ", m[i]); //输出层序
}
#include //map
#include //scanf(), printf()
#include
第1,3,4,6,28,29行,是迭代器需要的,注意几个点:
1, 迭代器需要的
#include
#include
2,
代码第28行,不要写成 it < m.end(),要写成 it != m.end(),否则报错
error: no match for operator...
P1427 小鱼的数字游戏 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
标签:入门,递归,栈
→ C++的栈 - 一杯明月 - 博客园 (cnblogs.com)
→ (2条消息) C++ 栈_雪易的博客-CSDN博客_c++栈
#include
#include
using namespace std;
int main()
{
int n;
stackm;
while(cin>>n) {
if(n == 0) break;
m.push(n);
}
while(!m.empty()) {
cout<
P3152 正整数序列 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
标签:数学,递归,普及-
思考方法1
(当前最大值)8 --> 4 --> 2 --> 1 --> 0 9 --> 4 --> 2 --> 1 --> 0
15 --> 7 --> 3 --> 1 --> 0
发现规律了吗
#include
using namespace std;
int main()
{
int n, ans = 0;
cin>>n;
while(n) {
n /= 2;
ans++;
}
cout<
思考方法2
假设当前最大值为maxn,每次大于等于maxn / 2 + 1的数,都减去maxn / 2 + 1,所以答案=log2(n) + 1
#include
#include //log2(n)
using namespace std;
int main()
{
int n, ans = 0;
cin>>n;
ans = log2(n) + 1;
cout<
P1103 - 地盘划分 - New Online Judge (ecustacm.cn)
标签:基础题,递归
一道非常典型的递归题
看图
第一次递归取3个,第二次取1个,第三次取2个
#include
using namespace std;
long long ans;
void sim(int n, int m) //不是最简就递归
{ //最简的意思是 m % n == 0
if(n > m) {
n = n^m;
m = n^m;
n = n^m; //异或交换两数
} //n小m大
ans += m / n;
if(m % n == 0)
cout<>n>>m) {
ans = 0;
sim(n, m);
}
return 0;
}
P1104 - 分形图 - New Online Judge (ecustacm.cn)
标签:基础题,递归
很典型
en,看了20分钟貌似没有非常明确的思路,所以还是学习别人的优秀代码吧
思路
1,
2,
3,
blablabla...
嘿!6个类型写了2.3万字
hello, world! 世界 你好 - 刘至佳 - 单曲 - 网易云音乐
阿衣莫 - 阿吉太组合 - 单曲 - 网易云音乐
1,对拍,找bug很棒的方法,注意n和N的不同。。粗心是大忌
2,s.find(), s.substr()
#include //s.substr(), s.find()
string s1 = s.substr(j, i); //下标j开始, 截取i个字符
if(s.find(s1, j + 1) == string::npos)
//没有找到子串, 返回string::npos
if(s.find(s1, j + 1) != string::npos)
//找到子串
3,map的迭代器
#include
#include