寒假知识点总结(算法)
$\huge\color{red}\colorbox{none}{寒假算法总结}$
-------------------以下内容来自本部初2024级17班$\color{orange}\colorbox{none}{陈锦辉}$-----------------------
1.模拟
模拟经典例题①:#160.「NOIP2004 普及组」不高兴的津津
题目在此省略...
这道题是一道非常容易的模拟题。
只需要按照我们人类的思维去模拟即可。
代码如下:
//the code is from chenjh
#include<bits/stdc++.h>
#include<iostream>
#include<cmath>
using namespace std;
int a[8],b[8],c[8];//分别为上学的时间、其他补习班的时间和总时间
int main(){
int max=0;
for(int i=1;i<=7;i++){
scanf("%d%d",&a[i],&b[i]);//输入
c[i]=a[i]+b[i];//求出总时间
if(c[i]>c[max] && c[i]>8){//时间超过以前最大值且超过8小时
max=i;
}
}
printf("%d",max);
return 0;
}
模拟经典例题②:#167. 「NOIP2011」铺地毯
题目在此省略...
这道题如果用二维数组模拟坐标系的话...就会拿到一个$\color{red}{MLE}$
所以只有先输入完$a,b,g,k$后再来依次判断这个范围包不包含$(x,y)$。
思路就很清晰了。
代码:
//the code is from chenjh
#include<bits/stdc++.h>
#include<iostream>
#include<cmath>
#include<cstdio>
using namespace std;
struct DT{//结构体(后面会提到)
int ax,b,g,k;//每张地毯的a,b,g,k
}a[10001];
int main(){
int n,x,y,s=-1;
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d%d%d%d",&a[i].ax,&a[i].b,&a[i].g,&a[i].k);//输入
}
scanf("%d%d",&x,&y);
for(int i=n;i>=1;i--){//最上层,倒着循环
if((a[i].ax<=x && a[i].ax+a[i].g>=x) && (a[i].b<=y && a[i].b+a[i].k>=y)){//判断(x,y)是否在范围内
s=i;
break;
}
}
printf("%d",s);
return 0;
}
更多模拟题:
2.高精度
$\color{blue}\colorbox{none}{高精度}$加减法、乘除法 $\color{blue}{PDF}$
题目在此省略...
当时在我们班上,这道题难倒了许多人
但其实这道题,只不过就是一道$N$进制$\color{red}\colorbox{none}{高精度加法题}$。
回顾一下高精度加法部分的代码:
nc=max(na,nb);
for(int i=0;i<nc;i++){
c[i]+=a[i]+b[i];
c[i+1]+=c[i]/10;
c[i]%=10;
}
if(c[nc]>0) nc++;
其实就只是将高精度加法中的$\color{red}\colorbox{none}{ 10进制} $进位改为了$\color{red}\colorbox{none}{n进制进位}$
所以...代码如下:
//the code is from chenjh
#include<bits/stdc++.h>
#include<iostream>
#include<cmath>
#include<cstdio>
#include<cstring>
using namespace std;
char s[1005];
int a[1005],b[1005],c[1005],n,l;
void jf(){//加法函数
for(int i=0;i<l;i++){
c[i]+=a[i]+b[i];
c[i+1]+=c[i]/n;
c[i]%=n;//注意是逢n进1
}
if(c[l]>0) l++;//处理首位进位的情况
return ;
}
bool hw(){//判断回文数函数
for(int i=0;i<=(l-1)/2;i++){
if(c[i]!=c[l-i-1])
return 0;
}
return 1;
}
int main(){
cin>>n;
cin>>s;
l=strlen(s);
for(int i=0;i<l;i++){
if(s[l-i-1]>=48 && s[l-i-1]<=57)
a[i]=s[l-i-1]-'0';//数字字符转数字
else
a[i]=s[l-i-1]-55;//字母字符转数字(16进制)
}
bool flag=1;
for(int i=0;i<=(l-1)/2;i++){
if(a[i]!=a[l-i-1])
flag=0;
}
if(flag){
printf("STEP=0");
return 0;
}//首先判断输入本身是不是回文数
else{
for(int i=1;i<=30;i++){//循环30次
for(int j=0;j<l;j++)
b[j]=a[l-j-1];//转回文数
jf();//高精度加法
if(hw()==1){//判断,是回文数则提前跳出
printf("STEP=%d",i);
return 0;
}
else{
swap(a,c);//调换结果,使结果成为新的加数
for(int j=0;j<=1004;j++){//b,c清零
b[j]=0;
c[j]=0;
}
}
}
}
printf("Impossible!");//剩下的一定是无法30步内完成的
return 0;
}
更多高精度题目:
#144. 「USACO2009OCT」Even? Odd?
3.枚举

题目在此省略...
这道题是一道经典的枚举题目,只需要将数字枚举出来判断火柴棒是否与所给数据相等即可。
于是就可以写出代码:
//the code is from chenjh
#include<bits/stdc++.h>
#include<iostream>
#include<cmath>
#include<cstdio>
using namespace std;
int num[10]={6,2,5,5,4,5,6,3,7,6};//每个数字需要的火柴棒
int get(int a){//判断需要的火柴棒
if(a==0){
return num[0];
}
int sum=0;
while(a!=0){
sum+=num[a%10];
a/=10;
}
return sum;
}
int main(){
int n;
scanf("%d",&n);
int ans=0;
for(int a=0;a<1000;a++){
for(int b=0;b<1000;b++){
int c=a+b;
int na=get(a);
int nb=get(b);
int nc=get(c);
if(na+nb+nc+4/*包括'+''='*/==n){//判断是否相等
ans++;
}
}
}
printf("%d",ans);
return 0;
}
更多枚举题目:
#105. 「USACO1.3」Ski Course Design
4.二分答案
题目在此省略...
这种数列分段会衍生出多种考法,比如以下几道题:
#192. 「USACO2007MAR」Monthly Expense
好,回归这道题,此题是一个典型的$\color{green}\colorbox{none}{二分答案}$题目。
二分答案需要设计一个自定义函数:
int f(int x){
...;
return ...;
}
二分答案时,有两种形式是必须要牢记的:
//向上取整,寻找最后一个xxx
while(l<r){
int mid=(l+r+1)/2;
if(f(mid)<m)
r=mid-1;
else
l=mid;
}
//向下取整,寻找第一个xxx
while(l<r){
int mid=(l+r)/2;
if(f(mid)<m)
l=mid+1;
else
r=mid;
}
总结起来,代码如下:
//the code is from chenjh
#include<bits/stdc++.h>
using namespace std;
int n,m,a[100005];
int cl(int x) {//设计分段函数
int sum=0;
int pre=0;
for(int i=1; i<=n; i++){
if(a[i]>x){
sum=m+1;
break;
}
if(pre+a[i]<=x)//判断是否分段
pre+=a[i];
else{
sum++;
pre=a[i];
}
}
sum++;
return sum;//返回分段值
}
int main() {
scanf("%d %d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
int l=0,r=2*1e9;
//二分答案部分
while(l<r){
int mid=(l+r)/2;
if(cl(mid)>m)
l=mid+1;
else
r=mid;
}
printf("%d\n",l);
return 0;
}
5.贪心
先来了解一下贪心的概念:
$\huge\color{yellow}{贪心}$算法(又称贪婪算法)是指,在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,算法得到的是在某种意义上的局部最优解。——百度百科
贪心经典例题①:#205. 选择不相交区间
由分析可得,每次尽可能选右端点尽量小的区间。
所以代码很快就可以打出:
#include<bits/stdc++.h>
using namespace std;
struct SEG{//考点:结构体(先声明,再定义,后使用)
int l;
int r;
}a[1000001];
bool cmp(SEG a,SEG b){//考点:sort+自定义比较函数
return a.r<b.r;
}
int main(){
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d %d",&a[i].l,&a[i].r);//输入结构体
}
sort(a+1,a+n+1,cmp);//考点:sort+自定义比较函数
int last=a[1].r;
int s=1;
for(int i=2;i<=n;i++)//贪心判断
{
if(a[i].l>=last)
{
last=a[i].r;
s++;
}
}
printf("%d",s);
return 0;
}
贪心经典例题②:#207. 区间选点问题
由分析可得,同样可以先将右端点排序,选择右端点作为一个“串起来”的点,如果没被串,就再在右端点增加一个点。
代码又可以轻而易举地写出来:
#include<bits/stdc++.h>
using namespace std;
struct SEG{
int l;
int r;
}a[1000001];
bool cmp(SEG a,SEG b){
return a.r<=b.r;
}
int main(){
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d %d",&a[i].l,&a[i].r);
}
sort(a+1,a+n+1,cmp);
int last=-2000000000;
int s=0;
for(int i=1;i<=n;i++)
{
if(a[i].l>last)
{
last=a[i].r;
s++;
}
}
printf("%d",s);
return 0;
}
贪心经典例题③:#208. 区间覆盖问题
分析贪心策略
假如我们已经选择了一些区间覆盖了$[1,234]$
接下来我们还要选择一个区间使得覆盖的范围更大
那就应该选择剩下的区间中,左端点在$[1,234]$之间,右端点尽可能大的那个区间
确切的说
最开始的时候,我们需要选择一个左端点在$[-inf,s]$之间的右端点最大的区间
假如上次选择的区间右端点为$r[i]$
然后再选择一个左端点在$[-inf,r[i] ]$之间的右端点最大的区间
假如为$r[i+1]$
...
直到选出来的右端点没有变大,或者超过了题目给定的$t$,结束循环
所以代码可写为:
//the code is from chenjh
#include<bits/stdc++.h>
using namespace std;
struct SEG{
int l,r;
}a[1000005];
bool cmp(SEG x,SEG y){
return x.l<y.l;
}
int main(){
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d %d",&a[i].l,&a[i].r);
int s,t;
scanf("%d %d",&s,&t);
sort(a+1,a+n+1,cmp);
int ans=0,last=s/*当前的能向右扩展的最右方*/,maxx=-1;
for(int i=1;i<=n;i++){
if(a[i].l>last){//所有左端点在last左边的区间已经处理完了
if(maxx<=last) break;
ans++;
last=maxx;
maxx=-1;
if(last>=t) break;
}
if(a[i].l<=last && a[i].r>maxx)//更新右端点的最大值
maxx=a[i].r;
}
if(last<t && maxx>last){
last=maxx;
ans++;
}
if(last<t)
printf("no solution!\n");
else
printf("%d\n",ans);
return 0;
}
小结:
有些贪心问题的贪心策略并不是那么显然
证明:$\large\color{red}\colorbox{none}{举反例}$
贪心的更多应用题:
#923. 「POJ1328」radar installation
6.递归
$\color{blue}\colorbox{none}{递归}$入门、进阶$\color{blue}{PDF}$
递归含义:
程序调用自身的编程技巧称为$\large\color{green}\colorbox{none}{递归}$($recursion$)。递归作为一种算法在程序设计语言中广泛应用。 一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解。——百度百科
直观: 函数自己调用自己
核心: 大问题-> 小问题
直到小问题轻松求解为止
经典例题①:#605. 全排列问题
利用递归的特殊性,写出n重循环递归函数。
下面附上代码:
//the code is from chenjh
#include<bits/stdc++.h>
using namespace std;
bool a[10];
int val[20];
void get(int pos,int n){//递归函数,模拟n重循环
if(pos==n+1){
for(int i=1;i<=n;i++)
printf(" %d",val[i]);//特殊要求输出
printf("\n");
}
else{
for(int i=1;i<=n;i++){
if(a[i]==0){
a[i]=1;//标记数字已用
val[pos]=i;
get(pos+1,n);
a[i]=0;//记得还原!!!
}
}
}
}
int main(){
int n;
scanf("%d",&n);
memset(a,0,sizeof(a));//快速初始化清零数组
get(1,n);
return 0;
}
经典例题②$\large\color{red}\colorbox{none}{(重点)}$:#460. 子集和问题
这道题就是利用递归一层一层的枚举子集是否与和相等,再利用标记数组即可。
下面是32</font>分</del>代码:
//the code is from chenjh
#include<bits/stdc++.h>
using namespace std;
int n,c;//存储个数和目标
bool flag=0;
int s[10000];
bool a[10000];
void dfs(int pos,int cs){
if(cs==c && !flag){//flag不为0且达到目标要求
for(int i=1;i<=pos;i++){
if(a[i])//如果标记数组为1(已用)
printf("%d ",s[i]);//输出是已选数字
}
flag=1;//表示有解
return;
}
else if(flag) return;//有解就直接返回
else{
if(pos==n+1){
return;
}
a[pos]=1;//标记上
dfs(pos+1,cs+s[pos]);//选与不选
a[pos]=0;//记得清零!!!
dfs(pos+1,cs);
}
}
int main(){
scanf("%d %d",&n,&c);
for(int i=1;i<=n;i++)
scanf("%d",&s[i]);
dfs(1,0);
if(!flag){//运行后的无解情况
printf("No Solution!\n");
}
return 0;
}
更多递归题目:
7.动态规划
动态规划 定义:
<font size=5 color=blue>动态规划</font>($Dynamic$ $Programming$,$DP$)是运筹学的一个分支,是求解决策过程最优化的过程。20世纪50年代初,美国数学家贝尔曼($R.Bellman$)等人在研究多阶段决策过程的优化问题时,提出了著名的最优化原理,从而创立了动态规划。动态规划的应用极其广泛,包括工程技术、经济、工业生产、军事以及自动化控制等领域,并在背包问题、生产经营问题、资金管理问题、资源分配问题、最短路径问题和复杂系统可靠性问题等中取得了显著的效果 。
经典例题①:#223. 「模板」最长不下降子序列 LIS
思路:
• 先看最简单的情况,一个数字的时候,最长不降子序列显然为1
• 如果我记录了以每个数字为结尾的最长不降子序列长度
• 那么,新增加一个数字的时候,我们就能很容易找到以该数字结尾的最长不降子序列长度
• 从前往后依次寻找前面小于等于它且不降子序列长度最大的,在此基础上加一即可
• 如果前面没有一个元素小于等于它,就将其置为1
• 最后所有数字结尾的最长不降子序列长度中的最大值就是答案
附上代码:
#include<bits/stdc++.h>
using namespace std;
int a[5001],dp[5001];
int main(){
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
for(int i=1;i<=n;i++){
dp[i]=1;//一开始就置为1
for(int j=i-1;j>=1;j--){//所有可能更新的dp[i]的j
if(a[j]<=a[i]){
if(dp[j]+1>=dp[i])
dp[i]=dp[j]+1;
}
}
}
int maxn=0;
for(int i=1;i<=n;i++)
maxn=max(maxn,dp[i]);
printf("%d\n",maxn);
return 0;
}
经典例题②:#255. 背包问题(最基础的01背包)
试了试贪心,发现不可行。所以..只能用动态规划!!!
附上代码:
//the code is from chenjh
#include<bits/stdc++.h>
using namespace std;
int dp[205];
int w[50],c[50];
int main(){
int M,n;
scanf("%d %d",&M,&n);
for(int i=1;i<=n;i++)//输入
scanf("%d %d",&w[i],&c[i]);
for(int i=0;i<=M;i++)//初始化
dp[i]=0;
for(int i=1;i<=n;i++){//依次枚举每个物品
for(int j=M;j>=w[i];j--)
dp[j]=max(dp[j],dp[j-w[i]]+c[i]);//决策放不放
}
printf("%d\n",dp[M]);
return 0;
}
其他背包学习资料:
8.$DFS$搜索
$DFS$搜索经典例题①:#454. function
题目在此省略...
此题是典型的记忆化搜索。
只需将每次计算的答案存入数组即可,如果有(不为0)就不需要在计算一次。
<del>顺便告诉你,$w(20,20,20)=1048576$</del>
代码如下:
//the code is from chenjh
#include<bits/stdc++.h>
#include<iostream>
#include<cmath>
#include<cstdio>
#include<cstring>
using namespace std;
long long f[25][25][25];//用f数组储存答案
long long w(long long a,long long b,long long c){
if(a<=0 || b<=0 || c<=0){
return 1;
}
else if(a>20 || b>20 || c>20){
return w(20,20,20);
}
else if(f[a][b][c]!=0) return f[a][b][c];//如果已经计算过即可直接返回
else if(a<b && b<c){
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);//没有则按照题意进行递归搜索
}
else{
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);
}
return f[a][b][c];
}
int main(){
long long a,b,c;
f[20][20][20]=1048576;
while(1){
scanf("%lld%lld%lld",&a,&b,&c);//重复输入
if(a==-1 && b==-1 && c==-1)//直至(-1,-1,-1)
break;
long long ans=w(a,b,c);
printf("w(%lld, %lld, %lld) = %lld\n",a,b,c,ans);//按照格式输出答案
}
return 0;
}
更多$DFS$搜索题目:
还在更新中...
更多的讲义(PDF):
$\huge\color{green}\colorbox{none}{记得点个赞!!!}$
(要求不过分吧)

浙公网安备 33010602011771号