[算法课]实验课全题目详解

文章目录

  • 第一部分 递归
    • 第一题 数的计算
    • 第二题 选数
    • 第三题 滑雪 *
  • 第二部分 分治法
    • 第一题 烦恼的高考志愿
    • 第二题 平面上的最接近点对
    • 第三题 砍树 *
  • 第三部分 蛮力法
    • 第一题 完美情侣
    • 第二题 化简
    • 第三题 四方定理*
  • 第四部分 回溯法
    • 第一题 单词方阵
    • 第二题 小偷问题
    • 第三题 小猫爬山*
  • 第五部分 贪心法
    • 第一题 陶陶摘苹果(升级版)
    • 第二题 铺地毯
    • 第三题 局域网*
  • 第六部分 动态规划
    • 第一题 采药
    • 第二题 货币系统
    • 第三题 庆功会*
  • 总结

第一部分 递归

第一题 数的计算

/*
s=0 n=1 0; s=1 n=1 1;
s=2 n=2 exm:12,2,s=3 n=2 exm:13,1;
s=4 n=4 exm:14,13,24,124;s=5 n=4 exm:15,25,125,5;
s=5 n=3 

观察发现:
if n%2==1 ansN=ansN-1
else ansN+=ansN(N/2)
*/
#include 

using namespace std;
const int N=1e4;
int f[N];
int main()
{
    f[1]=1;
    int n;
    cin>>n;
    for(int i=2;i<=n;i++)
        if(i%2==1)f[i]=f[i-1];
        else f[i]=f[i-1]+f[i/2];
    
    cout<<f[n];
    return 0;
}

[算法课]实验课全题目详解_第1张图片

#include

using namespace std;

int res=0;

void dfs(int n)
{
    res++;
    for(int i=1;i<=n/2;i++)dfs(i); 
}

int main()
{
    int n;
    cin>>n;
    
    dfs(n);
    
    cout<<res;
    
    return 0 ;
}

[算法课]实验课全题目详解_第2张图片

第二题 选数

#include
#include

using namespace std;

const int N=1e4;

int a[N];
int ans;
int n,k;

bool isPrime(int n)
{
    for(int i=2;i<=sqrt(n);i++)if(n%i==0)return false;
    return true;
}

void dfs(int u,int sum,int idx)
{
    if(u==k)
    {
        if(isPrime(sum))ans++;
    }
    else
    {
        for(int i=idx;i<n;i++)
            dfs(u+1,sum+a[i],i+1);
    }
}

int main()
{
    cin>>n>>k;
    
    for(int i=0;i<n;i++)cin>>a[i];
    
    dfs(0,0,0);
    
    cout<<ans;
    
    return 0;
}

[算法课]实验课全题目详解_第3张图片

#include
#include

using namespace std;

const int N=1e4;

int a[N];
int ans;
int n,k;

bool isPrime(int n)
{
    for(int i=2;i<=sqrt(n);i++)if(n%i==0)return false;
    return true;
}

void dfs(int u,int sum,int idx)
{
    if(u==k)
    {
        if(isPrime(sum))ans++;
        return ;
    }
    
    if(idx>=n)return ;   
    dfs(u,sum,idx+1);
    
    dfs(u+1,sum+a[idx],idx+1);
}

int main()
{
    cin>>n>>k;
    
    for(int i=0;i<n;i++)cin>>a[i];
    
    dfs(0,0,0);
    
    cout<<ans;
    
    return 0;
}

[算法课]实验课全题目详解_第4张图片

#include
#include

using namespace std;

const int N=1e4;

int a[N];
bool st[N];
int ans;
int n,k;
bool RES[N];

bool isPrime(int n)
{
    for(int i=2;i<=sqrt(n);i++)if(n%i==0)return false;
    return true;
}

void dfs(int u,int sum)
{
    if(u==k)
    {
        if(isPrime(sum))RES[sum]=true;
        return ;
    }
    else
    {
        for(int i=0;i<n;i++)
        {
            if(st[i]==false)
            {
                st[i]=true;
                dfs(u+1,sum+a[i]);
                st[i]=false;
            }
        }
    }
}

int main()
{
    cin>>n>>k;
    
    for(int i=0;i<n;i++)cin>>a[i];
    
    dfs(0,0);
    
    for(int i=0;i<N;i++)if(RES[i])ans++;
    
    cout<<ans;
    
    return 0;
}

[算法课]实验课全题目详解_第5张图片

#include
#include
#include

using namespace std;

const int N=1e4;
int a[N];
int ans;
int n,k;

bool isPrime(int n)
{
    for(int i=2;i<=sqrt(n);i++)if(n%i==0)return false;
    return true;
}

int main()
{
    cin>>n>>k;

    do{
        int tmpAns=0;
        for(int i=0;i<k;i++)tmpAns+=a[i];
        if(isPrime(tmpAns))ans++;
    }while(next_permutation(a,a+n));
    
    cout<<ans;
    
    return 0;
}

没有过数据,但是好歹样例能过,错误的原因也是如出一辙,都是大量的重复数据!
这里没办法判断重复,因为最好的思路就是转换成字符串进行操作,然后给到SET里面进行判断重复,但是一旦这样做的话,和上面的逻辑一样

在这里插入代码片

[算法课]实验课全题目详解_第6张图片

#include
#include
#include

using namespace std;

const int N=1e4;
int a[N];
int cnt;
int n,k;

bool checkResArr[N];

bool isPrime(int n)
{
    for(int i=2;i<=sqrt(n);i++)if(n%i==0)return false;
    return true;
}

int main()
{
    cin>>n>>k;

    do{
        int tmpAns=0;
        for(int i=0;i<k;i++)tmpAns+=a[i];
        if(isPrime(tmpAns))checkResArr[tmpAns]=true;
    }while(next_permutation(a,a+n));
    
    for(int i=0;i<N;i++)if(checkResArr[i])cnt++;
    
    cout<<cnt;
    
    return 0;
}

[算法课]实验课全题目详解_第7张图片
即使我加了一部分判断,依然在字符串的情况下难以判重。

4 3 3 7 12 19

第三题 滑雪 *

#include

using namespace std;

const int N=1e3+10;
int g[N][N];
int dist[N][N];//
int res=0;
int n,m;

int dx[]={-1,0,1,0},dy[]={0,1,0,-1};

int dfs(int x,int y)
{
    if(dist[x][y])return dist[x][y];
    
    dist[x][y]=1;
    
    for(int i=0;i<4;i++)
    {
        int tx=dx[i]+x,ty=dy[i]+y;
        //if(g[x][y]>g[tx][ty] && (unsigned int)tx
        if(g[x][y]>g[tx][ty]&& ((tx-0)|(n-tx-1)|(ty-0)|(m-ty-1))>=0)
            dist[x][y]=max(dist[x][y],dfs(tx,ty)+1);
            
    }
    
    return dist[x][y];
}

int main()
{
    cin>>n>>m;
    
    for(int i=0;i<n;i++)         
        for(int j=0;j<m;j++)
            cin>>g[i][j];
    
    for(int i=0;i<n;i++)         
        for(int j=0;j<m;j++)
            res=max(res,dfs(i,j));            
    dfs(0,0);
    
    cout<<res;
    
    return 0;
}

[算法课]实验课全题目详解_第8张图片

第二部分 分治法

第一题 烦恼的高考志愿

#include
#include

using namespace std;

const int N=1e6+10;
int a[N],b[N];
int ans ;

int binnarySearch(int x,int l,int r)
{
    while(l<=r)
    {
        int mid = l+r>>1;
        if(a[mid]<x)l=mid+1;
        else r=mid-1;
    }
    return l;
}

int main()
{
    int n,m;
    cin>>n>>m;
    
    for(int i=1;i<=n;i++)cin>>a[i];
    
    sort(a+1,a+n+1);
    //for(int i=0;i
    //cout<
    
    for(int i=1;i<=m;i++)
    {
        cin>>b[i];
        int idx = binnarySearch(b[i],0,m-1);
        cout<<a[idx]<<" ";
        ans += abs(b[i]-a[idx]);
    }
    cout<<ans;
    return 0;
}
#include
#include

using namespace std;

const int N=1e6+10;
int a[N],b[N];
int ans ;

int binnarySearch(int x,int l,int r)
{
    int mid = l+r>>1;
    if(l>r || a[mid]==x)return l;
    
    if(a[mid]<x)binnarySearch(x,mid+1,r);
    else binnarySearch(x,l,mid-1);
}

int main()
{
    int n,m;
    cin>>n>>m;
    
    for(int i=1;i<=n;i++)cin>>a[i];
    
    sort(a+1,a+n+1);
    //for(int i=0;i
    //cout<
    
    for(int i=1;i<=m;i++)
    {
        cin>>b[i];
        int idx = binnarySearch(b[i],0,m-1);
        //cout<
        ans += abs(b[i]-a[idx]);
    }
    cout<<ans;
    return 0;
}

第二题 平面上的最接近点对

#include
#include

using namespace std;

const int N=1e5;
int x[N],y[N];

int main()
{
    int n;
    cin>>n;
    
    for(int i=0;i<n;i++)cin>>x[i]>>y[i];
    
    double res=1e9;
    for(int i=0;i<n;i++)
    {
        for(int j=i+1;j<n;j++)
        {
            int dx= (y[i]-y[j])*(y[i]-y[j]);
            int dy= (x[i]-x[j])*(x[i]-x[j]);
            res=min(res,sqrt(dx+dy));
        }
    }
    
    cout<<fixed<<setprecision(4)<<res;	
    
    return 0;
}

[算法课]实验课全题目详解_第9张图片

第三题 砍树 *

木材总长度达到了2E9 ,所以用 long long int

#include
#include

using namespace std;

const int N=1e6+10;

typedef long long int LL;
LL h[N];
int n,m;

LL check(int Num)
{
    LL sum=0;
    for(int i=0;i<n;i++)if(h[i]>Num)sum+=(h[i]-Num);
    
    return sum;
}

int binnarySearch(int l,int r,int keyN)
{
    int i=l,j=r;
    while(i<=j)
    {
        int mid = (i+j)>>1;
        if((check(mid))<keyN)j=mid-1;
        else i=mid+1;
    }
    
    return i-1;
}

int main()
{
    cin>>n>>m;
    
    for(int i=0;i<n;i++)cin>>h[i];
    sort(h,h+n);

    int r = h[n-1];
    cout << binnarySearch(0,r,m);

    return 0;
}

[算法课]实验课全题目详解_第10张图片

#include
#include

using namespace std;

const int N=1e6+10;
int h[N];
int n,m;

int check(int Num)
{
    int sum=0;
    for(int i=0;i<n;i++)if(h[i]>Num)sum+=(h[i]-Num);
    
    return sum;
}

int binnarySearch(int l,int r,int keyN)
{
    int i=l,j=r;
    while(i<=j)
    {
        int mid = (i+j)>>1;
        if((check(mid))<keyN)j=mid-1;
        else i=mid+1;
    }
    
    return i-1;
}

int main()
{
    cin>>n>>m;
    
    for(int i=0;i<n;i++)cin>>h[i];
    sort(h,h+n);

    int r = h[n-1];
    cout << binnarySearch(0,r,m);

    return 0;
}

[算法课]实验课全题目详解_第11张图片

第三部分 蛮力法

第一题 完美情侣

#include

using namespace std;

const int N=1e3+10;
typedef long long int LL;

int a[N],b[N];

int main()
{
    LL n,m,k;
    
    cin>>n>>m>>k;
    
    for(int i=0;i<n;i++)cin>>a[i];
    for(int i=0;i<m;i++)cin>>b[i];

    LL ans=0;
    for(int i=0;i<n;i++)
        for(int j=0;j<n;j++)
            if(a[i]+b[j]==k)ans++;
                    
    cout<<ans;
    
    return 0;
}

[算法课]实验课全题目详解_第12张图片
[算法课]实验课全题目详解_第13张图片
均符合目标输出

第二题 化简

#include

using namespace std;

const int N=1e6+10;
int a[N];
int cnt;

bool check(int num,int v)//检查最简
{
    for(int i=2;i<=v;i++)if(num%i==0&&v%i==0)return false;
    
    return true;
}

int main()
{
    int n,m;
    cin>>n>>m;
    
    for(int i=0;i<m;i++)cin>>a[i];
    
    for(int i=0;i<m;i++)
    {
        for(int j=1;j<=n;j++)
            {
                if(j>=a[i]||j>n)break;//把区间定在1-(N-1) 且小于n
                else if(check(a[i],j))cnt++;
            }
        cout<<cnt<<endl;
        cnt=0;
    }       
    return 0;
}

符合样例
[算法课]实验课全题目详解_第14张图片

第三题 四方定理*

#include

using namespace std;

const int N=32768;
bool st[N+10];

int main()
{
    int t,n;
    cin>>t;
    
    for(int i=0;i*i<=N;i++)if(!st[i*i])st[i*i]=true;
    while(t--)
    {
        int cnt=0;
        cin>>n;
        
        for(int a=0;a*a<n;a++)
            for(int b=0;b*b+a*a<n;b++)
                for(int c=0;c*c+b*b+a*a<n;c++)  
                    {
                        int d = n-a*a-b*b-c*c;
                        if(!st[d])continue;
                        if(a*a<=b*b&&a*a<=c*c&&c*c<=d)
                            if(b*b<=c*c&&b*b<=d)
                                if(c*c<=d)
                                    cnt++;
                    }
        cout<<cnt<<endl;            
    }
    
    return 0;
}

[算法课]实验课全题目详解_第15张图片

第四部分 回溯法

第一题 单词方阵

八方向DFS 这已经不能算在考回溯了
这道题的思路我一开始就飞了

我以为是任意情况下的点,开始能不能凑成“yizhong”这个连续字符串,
所依在这种思考背景下,我进入了误区,直接以(0,0)为起始点,走到右下最终点,过程中dfs八个偏移量判断是否符合条件。
同时输入时检查所有数列,将非“yizhong”的字符清空为“*”,在后续的dfs中直接返回,做一个合理性支剪。

后来我发现两个最大的误区,也是这个题目最重要的部分
1.我们不用从左上到右下,因为字符串永远是y开头,我们只要从y开始判定即可
2.符合的要求只需要是八个方向上的直线,不可能出现曲折的线条

依据第二条和第一条,我们就可以发现我们只用判定以Y为中心(n-1)X(n-1)的范围上是不是有满足条件的直线即可

现在我们依据这个思路列出可能的示意图
这是以(2,2)为Y点的示意图,我们可以观察中心点与八条线上的点的关系
[算法课]实验课全题目详解_第16张图片
为了理清位置和偏移量的关系我重置了位置图,并画了偏移量的表
[算法课]实验课全题目详解_第17张图片
即八条直线上的所有位置都可以通过原坐标点以及偏移量来表示
这个时候我们引入stdStr 即题目中要求比对的字符串 “yizhong”的概念,
如果我们的第U个字符串是对应的第U个stdStr字符的话就继续比对,如果一条线上的n-1个都是,即2完成比对

那么我们怎么完成八个方向上的n-1个字符比对呢?
首先我们线比对偏移量为(-1,1)即左上的位置
我们设定

	1.偏移量(-1,1)是所有偏移量中的第P个,只要我们使用(x+dx[p],y+dy[p])即可表示
	2.我们要比对的字符是总数n-1个字符中的第q个,只要我们使用q就可以表示当前第是第几个,而q在完整的过程当中是循环自增到n-1为止的

这样一来我们可以列出检查偏移量(-1,1)时是否满足条件的情况
[算法课]实验课全题目详解_第18张图片
那么如何检查八个方向呢?
我们只要再控制一个变量,将方向转移八次,然后重复检查单次的操作即可

我写的代码为了方便,并没有使用递归

#include
#include

using namespace std;

const int N = 1e3 + 10;
char g[N][N];//地图
bool backMap[N][N];//备份地图

int dx[] = { -1,0,1,1,1,0,-1,-1 };
int dy[] = { 1,1,1,0,-1,-1,-1,0 };

string stdStr = "yizhong";
int n;

void dfs(int x, int y)
{
	for (int i = 0; i < 8; i++)
	{
		bool checkWay = true;
		for (int j = 1; j < 7; j++)
		{
			int tx = x + dx[i] * j, ty = y + dy[i] * j;//利用乘j来计算相应坐标
			if((unsigned int)tx<n&&(unsigned int) ty<n) ;else {checkWay = false;continue;}//检查边界
			if (g[tx][ty] != stdStr[j]){checkWay = false;break;}//如果不符合目标StdStr 检查下一个方向
		}
		if (!checkWay)continue;

		for (int j = 0; j < 7; j++)//赋值
		{
			int tx = x + dx[i] * j, ty = y + dy[i] * j;
			backMap[tx][ty] = true;
		}
	}
	return;
}
int main()
{
	cin >> n;

	memset(g, '*', sizeof g);//初始化

	for (int i = 0; i < n; i++)cin >> g[i];//string方式读入
    
	for (int i = 0; i < n; i++)
		for (int j = 0; j < n; j++)
			if (g[i][j] == 'y')
				dfs(i, j);//找到Y点然后开始搜索6X6的范围,是否存在符合条件

	for (int i = 0; i < n; i++)
	{
		for (int j = 0; j < n; j++)
			if (backMap[i][j])//如果满足条件就输出对应字符
				cout << g[i][j];
			else cout << '*';
		cout << endl;
	}

	return 0;
}

[算法课]实验课全题目详解_第19张图片

第二题 小偷问题

更新
之前理解错了题目的思路,以为是强制从左往右
如果是随便在中间插入的话,背包思路似乎不使用,我们直接暴力

#include
#include

using namespace std;
const int N=1e9+10;
const int MaxArrSpace = 1010;

typedef long long LL;

int a[MaxArrSpace];
int minn=N;
int n;

void dfs(int u,int sum)
{
    if(u==n)return ;
    dfs(u+1,sum+a[u]);
    cout<<sum+a[u]<<" ";
    minn=min(minn,sum+a[u]);
}

int main()
{
    cin>>n;
    
    for(int i=0;i<n;i++)cin>>a[i];
    
    for(int i=0;i<n;i++){dfs(i,0);cout<<endl;}
    
    cout<<minn;
    
    return 0;
}

很明白的思路
一个小偷从N个数列当中获取值,但是必须从左往右固定顺序
精简一下,就是一个数值序列,从左往右,获取任意长度下的最小和

我们可以使用NXN暴力比对,当然这里我们使用递归也差不多,N个数字,也就是可能存在的和的数字数量实际不定,我们对N个数字都递归,直到获取到第N个数字位置,过程比对

更新
之前理解错了题目的思路,以为是强制从左往右
如果是随便在中间插入的话,背包思路似乎不使用,我们直接暴力

#include
#include

using namespace std;
const int N=1e9+10;
const int MaxArrSpace = 1010;//数组空间

typedef long long LL;

int a[MaxArrSpace];
int minn=N;//最小值
int n;

void dfs(int u,int sum)//u 当前选择的第几个,sum是总和
{
    if(u==n)return ;
    dfs(u+1,sum+a[u]);//递归到下一个
    minn=min(minn,sum+a[u]);//更新
}

int main()
{
    cin>>n;
    
    for(int i=0;i<n;i++)cin>>a[i];
    
    for(int i=0;i<n;i++)dfs(i,0);
    
    cout<<minn;
    
    return 0;
}

但这里出现一个问题
如果所有的值都是正整数

如果我们将
    cout<<sum+a[u]<<" ";
    minn=min(minn,sum+a[u]);
改为
    cout<<sum<<" ";
    minn=min(minn,sum);

左侧是cout< 右侧是cout< 我们可以发现,针对右侧我们是少一个值的,而剩下的一个值又因为sum初始值是0的原因,造成了最小值在全是正数的情况下是0
[算法课]实验课全题目详解_第20张图片

那么,为什么在很多非纯正数的数据是正常的呢?
因为一但数值存在负数,那么数值最小值肯定是负数,不可能是0
右侧是

cout<<sum+a[u]<<" ";
minn=min(minn,sum+a[u]);

左侧的是

cout<<sum<<" ";
minn=min(minn,sum);

我们可以很清晰的发现两者的区别
[算法课]实验课全题目详解_第21张图片
那么事实上这里的问题出现在哪里呢?
逻辑代码
[算法课]实验课全题目详解_第22张图片
测试数据
[算法课]实验课全题目详解_第23张图片
我们检查这里会发现,事实上,我们先递归到了最高层得到sum -2,-2事实上是N个数字相加所得的结果
他是怎么出来的呢?
程序从0递归到最高层n-1,退出,此时开始回溯,将得到的答案输出,但这个时候,我们的回溯到底检查,发现第一层是单纯的sum=0,即sum得初始值,检查所有的值,发现都是从a[0]到a[n-1]累加,当前得a[u]却并没有获取!
我们可以直接观察数值
在这里插入图片描述

推测问题在这一行

dfs(u+1,sum+a[u]);

u+1,但是sum的a[u]却实际是前一个u的值,并没有即时更新
如果我们不更新n个值,而是n+1个值,我们就会发现a[n-1],即第n个值的出现
如果这样写的话,我们要排除的问题就是第一个值是废的
[算法课]实验课全题目详解_第24张图片
但我们不能盲目的增设初始值
[算法课]实验课全题目详解_第25张图片
一旦这样更改就会出现累加错误的情况
即给了初始值之后,下一次执行累加由增加了初始值a[0],a[0]被累加了两次
[算法课]实验课全题目详解_第26张图片

或者,我们换一个写法
这次我们在递归之前直接更新,输出
很清楚明了的发现,此时的第一次的sum其实是没有任何其他值,只有自身的初始值
[算法课]实验课全题目详解_第27张图片
要更改的方式也相同
即增添当前需要的a[u]
[算法课]实验课全题目详解_第28张图片
所以现在我们总结一下
为什么在很多非纯正数的数据是正常的呢?
因为一但数值存在负数,那么数值最小值肯定是负数,不可能是0
那么事实上这里的问题出现在哪里呢?

dfs(u+1,sum+a[u]);

u+1,但是sum的a[u]却实际是前一个u的值,并没有即时更新
如果我们不更新n个值,而是n+1个值,我们就会发现a[n-1],即第n个值的出现

第三题 小猫爬山*

这道题比较好玩

我们要放猫,要自己开新的车

第一步在查找的时候,事实上你时没有车的
这个时候你只能考虑开一辆新车来放

而第二次抱猫,这个时候你就需要思考了,我们唯一拥有的车子是否有剩余的空间?
我们是该放入车里,还是新开一个车来?

每次抱一只新的猫的时候,你都需要从0到当前所有车辆的车子中考虑一遍

以下是 u为当前选择的猫 来考虑摆放在哪一个车上的思路
[算法课]实验课全题目详解_第29张图片
我们来优化整个过程
[算法课]实验课全题目详解_第30张图片

这是整个优化的思路

那么,我们可以直接使用贪心吗?
我编写了一个简单的贪心的思路,检查车有没有空余,如果当前的猫能放我就放入,不能的话就开新车

#include
#include

using namespace std;

const int N=1e5+10;
int n,w;
int a[N],sum[N];
int cnt;

bool check(int weight)//检查猫能不能放入车内
{
    for(int i=1;i<=cnt;i++)
        if(weight+sum[i]<=w){sum[i]+=weight;return true;}//能放入,退出
    
    return false;
}

int main()
{
    cin>>n>>w;//n 数量 w 每辆车的最大容量
    
    for(int i=0;i<n;i++)cin>>a[i];//读入数据
    
    sort(a,a+n,[](int a,int b){return a>b;});//反序排序
    
    sum[++cnt]=a[0];//初始化第一辆车
    
    for(int i=1;i<n;i++)if(!check(a[i]))sum[++cnt]=a[i];//如果容量不够放入,我们就需要初始化一辆新的车
    
    for(int i=1;i<n;i++)cout<<sum[i]<<" ";//写出每辆车的现存载重
    
    cout<<endl;
    
    cout<<cnt;//输出车的数量
    
    return 0;
}

数据读出
[算法课]实验课全题目详解_第31张图片
我们过掉了样例,那我们成功了吗?
没有
在maxV=16时,重量为9 5 5 5 4 3 的情况下进行测试
贪心结果是9+5 5+5+4 3,结果为3 与我代码的逻辑吻合
但正确结果为9+4+3 5+5+5,结果为2 实际的数量却实际更小
因此我们不能直接盲目使用贪心

在实际的数据当中我们也遇到了相同的问题
[算法课]实验课全题目详解_第32张图片
于是我们可以发现,贪心的逻辑在这里是不可行的
我们可以使用DFS暴力搜索每一种方案,即可以放置在现有的中的任意一辆车上,例如
[算法课]实验课全题目详解_第33张图片
这里我们输出了所有方案的数量,我们会发现在我们的贪心方法错误的数据当中,我们更新到了最优的答案

那么,为什么数据会逐渐变小呢?

	if(cnt>=res)return ;
    if(u>n-1){res=cnt;return ;}
在这两行代码中
	第二行代码,我们选择完了之后进行更新答案
	第一行代码,我们检查当前答案是否大于历史最优值,如果大于,就不继续检查,而是直接返回

正确代码

#include
#include

using namespace std;

const int N=18+2;
int a[N],sum[N];//a 小猫重量 sum 车现载重
int n,w;
int res;

void dfs(int u,int cnt)//u 当前第几只猫,cnt当前车的数量
{
    if(cnt>=res)return ;//如果车的数量大于现实存在的猫咪的个数,或者历史最优的数值,返回
    if(u>n-1){res=cnt;return ;}//如果考虑完了n只即所有猫咪,则更新答案
    
    for(int i=0;i<cnt;i++)
        if(sum[i]+a[u]<=w)//检查现有的所有车的重量,是否能放猫
            {
                sum[i]+=a[u];
                dfs(u+1,cnt);
                sum[i]-=a[u];
            }
    
    sum[cnt]=a[u];//新增一辆车,初始重量是当前猫的重量
    dfs(u+1,cnt+1);
    sum[cnt]=0;
}

int main()
{
    cin>>n>>w;
    
    res = n;
    
    for(int i=0;i<n;i++)cin>>a[i];
    
    sort(a,a+n,[](int a,int b){return a>b;});//反序排序 优化了搜索顺序 lambda表达式

    dfs(0,0);

    cout<<res;
    
    return 0;
}

反序排序优化,优化了就是20MS,如果未优化则是90MS
[算法课]实验课全题目详解_第34张图片
具体数值
[算法课]实验课全题目详解_第35张图片

第五部分 贪心法

第一题 陶陶摘苹果(升级版)

思路
首先我们来理解题目的参数

	1.参数n 代表一共有N个数据需要输入
	2.参数a 代表我们可以达到的高度+a
	3.参数b 代表我们起始高度为b
	4.参数s 代表我们一共有s的被减值
	5.参数x 代表每个苹果被摘掉各自需要的高度
	6.参数y 代表每个苹果被摘掉各自需要的力气

现在我们理解整个逻辑:
画一个图给大家看
[算法课]实验课全题目详解_第36张图片

从这张图我们可以了解到
	1.a+b 等于我们可以摘取的范围 height
	2.只要有苹果的高度低于 height 就是我们可以摘取的
	3.在所有我们可以摘取的苹果中,我们要摘取最多的数量,我们肯定就要考虑当前耗费力气最小的苹果
	4.一旦我们一直查找最小的苹果知道我们的力气不足以摘取任何苹果,我们就得到了最佳答案

我们依照以上的逻辑编写代码
[算法课]实验课全题目详解_第37张图片

现在我们依照这个逻辑来编写代码

AC代码

#include
#include
#include

using namespace std;

#define x first
#define y second
const int N=5e3+10;
typedef pair<int,int> PII;//定义一个 PII类型 同时拥有 x y  两个数据
PII BeginArr_PII[N];//定义 PII 类型的数组 用来获取初始化数据
vector<PII>TmpArr_PII;//定义PII的不定长数组 用来进行主要的计算
int res; //答案变量

int main()
{
    int n,s,a,b;
    cin>>n>>s>>a>>b;
    int TaoHeight = a+b;//淘淘实际能获取苹果的高度
    
    for(int i=0;i<n;i++)cin>>BeginArr_PII[i].x>>BeginArr_PII[i].y;//获取初始值

    //for(int i=0;i
    cout<<endl;
    
    for(int i=0;i<n;i++)if(BeginArr_PII[i].x<=TaoHeight)TmpArr_PII.push_back(BeginArr_PII[i]);//将所有初始数据中高度小于height的都放入不定长数组中
    
    sort(TmpArr_PII.begin(),TmpArr_PII.end(),[&](PII a,PII b){return a.y<b.y;});//所有可获得的苹果都按照耗费力气从小到大排序
    
    //for(PII i:TmpArr_PII)cout<
    
    for(PII i:TmpArr_PII)if(s>=i.y)s-=i.y,res++;//只要有力气摘苹果就从小到大的耗费进行摘取,答案自增
    
    cout<<res;//返回答案
    
    return 0;
}

[算法课]实验课全题目详解_第38张图片

但这样的代码看起来稍微有些长,我们有没有办法进行优化呢?
整理逻辑

	1.初始化数据
	2.把所有高度小于height的数据丢入新数组
	3.对新数组排序
	4.贪心累加

但细想可以发现,我们没有必要对一定高度放入,可以直接把原有数据排序,省去步骤二,只要我们自定义排序规则时添加条件即可
更新后的AC代码

#include
#include
#include
    
using namespace std;
    
#define x first
#define y second
const int N=5e3+10;
typedef pair<int,int> PII;
vector<PII>TmpArr_PII;
int res;
    
int main()
{
    int n,s,a,b;
    cin>>n>>s>>a>>b;
    int TaoHeight = a+b;
    for(int i=0;i<n;i++){int x,y;cin>>x>>y;TmpArr_PII.push_back({x,y});}
    
    sort(TmpArr_PII.begin(),TmpArr_PII.end(),[&](PII a,PII b){return a.y<b.y;});
        
    for(PII i:TmpArr_PII)if(s>=i.y&&i.x<=TaoHeight)s-=i.y,res++;
        
    cout<<res;
        
    return 0;
}

[算法课]实验课全题目详解_第39张图片
我们成功压缩了一半的代码量 并且快了4MS

然后是,抄作业的同志,这个做法我写完之后去题解看了一圈都没有这么做的
所以抄的时候不要直接抄这个,因为没人这么写
除了我

方法2
现在我们来考虑更容易理解的方法

我们采用桶排序的方式来规避多一个点 同时具有 力气 高度 两个参数的情况

#include

using namespace std;

int n,s,a,b;
const int N=100+10;
const int y_len=100;
int arr[N];

int main()
{
    cin>>n>>s>>a>>b;
    const int height=a+b;//小红能达到的长度
    
    for(int i=0;i<n;i++)
    {
        int x,y;
        cin>>x>>y;
        
        if(x<=height)arr[y]++;//以y作为下标 如果这个苹果的高度小于可以达到的 我们就表示 目标力气的苹果+1
    }
    
    
    int res=0;
    for(int i=0;i<=y_len;i++)
        if(s>=i)
            while(arr[i]--)
                if(s>=i)s-=i,res++;//这里的while持续自减是为了避免 我们一次减少多个数字把自己更新为了负数的情况
        else break;//
        
    cout<<res;
    
    return 0;
}

第二题 铺地毯

我们理解一下这道题

	1.参数n 总共有N个地毯
	2.参数a b 表示以(a,b)为起点
	3.参数g k 表示以(a,b)为起点,(a+g)X(b+k)的范围被新的地毯覆盖

我们需要理解到,不论前面的地毯姿势多么奇异,只要后面的地毯覆盖到了点(x,y)那么我们就得返回后面地毯的坐标

这种方式下,我们只需要从后往前推范围即可,从第n到第1个地毯,如果被覆盖就直接返回答案

那么,怎么判断点(x,y)是否被覆盖呢?
首先我刚开始的时候直接想错了,我想给一个BOOL的二维数组,然后(a+g)X(b+k)直接覆盖,然后检查是否覆盖了(x,y)点

但事实上,我们可以直接依靠a b g k四个点的数据来判断
即:(x in (range of a,a+g) && y in(range of b,b+k))
那么我们就可以判断点(x,y)在目标范围内,逆序检查的情况下直接返回

#include

using namespace std;
const int N=1e4+10;
int n;
int a[N],b[N],g[N],k[N];

int main()
{
    cin>>n;
    
    for(int i=0;i<n;i++)cin>>a[i]>>b[i]>>g[i]>>k[i];//读入数据
    
    int x,y;x=y=0;
    int res=-1;//默认返回-1
    for(int i=n-1;i>=0;i--)
    {
        cin>>x>>y;//读入目标点
        if(x>=a[i]&&x<=a[i]+g[i]&&y>=b[i]&&y<=b[i]+k[i]){res=i+1;break;}//判定范围
    }
    cout<<res;//返回答案
    
    return 0;
}

[算法课]实验课全题目详解_第40张图片

第三题 局域网*

最小生成树得模板题变形
科里斯卡尔和普里姆都可以解决

解释之后再更新,因为有点多

prim

1.把所有距离初始化为正无穷
2.找到集合外距离最近的点t, 在联通块中的点
3.用t来更新其他点到集合的距离
4.把t加到集合中去

1.初始化 正无穷
[算法课]实验课全题目详解_第41张图片
2.因为集合没有任何点,我们随便找一个点接入集合
[算法课]实验课全题目详解_第42张图片
3.用t 更新其他点到集合的距离
即 其他点到t的距离,否则就继续是INF
[算法课]实验课全题目详解_第43张图片
4.t 放入集合 我们已经使其变绿 放入

第二次迭代
2.选中剩下距离到集合最近的点
3.用t 更新其他点到集合的距离
但这里,我们的各个点的最近距离更新之后也并没发生变化
4.t 放入集合 我们已经使其变绿 放入
[算法课]实验课全题目详解_第44张图片

第三次迭代
这里更新比较之后也没有产生变化
[算法课]实验课全题目详解_第45张图片
第四次迭代
[算法课]实验课全题目详解_第46张图片
最小生成树是什么意思呢?
可以理解为我们每一次把新的点接入集合的过程中,存在的对于集合的边的最短的那一个

如图
点1到点2 距离为1的边
点3到点1 或者 点2 距离为2的边 (因为有两个边可以选择,我们取最小)
点4到点1 距离为3的边

[算法课]实验课全题目详解_第47张图片

#include
#include
#include

using namespace std;

const int  N = 500+10;//N的点的数量
const int INF=0x3f3f3f3f;//设置一个初始最大值

int g[N][N];//一个稠密图 g[i][j] 从i到j的距离
int dist[N];//用来存储各个点距离集合的距离
bool st[N];//用来判断各个点是否在集合之内
int n,m;

int prim()//prim算法
{
    memset(dist,0x3f,sizeof dist);
    
    int res=0;
    for(int i=0;i<n;i++)//n次迭代
    {
        int t=-1;//标记为一次迭代中,所有剩余的在集合外的点中距离集合最近的距离
        for(int j=1;j<=n;j++)//寻找剩余的点中 距离离集合最近的点
            if(!st[j]&&(t==-1||dist[t]>dist[j]))t=j;//如果这个点在集合外 且 是第一个或者距离比当此寻找的t更小 那么就把t更新
        
        if(i&&dist[t]==INF)return INF;//如果不是第一次迭代,那么说明dist t 起码应该是有值对的,如果最小值都是INF,说明不连通
        
        if(i)res+=dist[t];//如果不是第一次迭代,最小生成树添加该边的距离
        st[t]=true;//当前这个点 t(剩余的点中距离集合最短的点) 被纳入集合
        
        for(int i=1;i<=n;i++)dist[i]=min(dist[i],g[t][i]);//使用t来更新其他点到集合的距离
//t是 当次遍历中(也就是剩下未进入集合的点中)距离集合距离最短的 新的 点
    }       
    return res;//返回最小生成树的总长
}

int main()
{
    memset(g,0x3f,sizeof g);//给两个图赋无穷大
    
    cin>>n>>m;
    
    int sum=0;
    
    int u,v,w;
    while(m--)
    {
        cin>>u>>v>>w;
        g[u][v] = g[v][u] = min(g[u][v],w);//无向图的特征 从u到u 从u到v 都是一个距离,且有可能重边,所有取自身和W最小
        sum+=g[u][v];
    }
    
    int t = prim();
    
    
    cout<<sum-t;//总数减去树的长度
    return 0;
}

[算法课]实验课全题目详解_第48张图片

第六部分 动态规划

第一题 采药

我们先进行盲目的贪心方式

这里我们直接计算单价最高,然后按照单价降序排序,每次都获得单价最高

#include
#include
#include

#define x first 
#define y second

using namespace std;
const int N=1e3+10;

typedef pair<int,int> PII;
vector<PII> arr;

int main()
{
    int t,m;
    cin>>t>>m;
    
    int tx,ty;
    for(int i=1;i<=m;i++){cin>>tx>>ty;arr.push_back({tx,ty});}
    
    sort(arr.begin(),arr.end(),[](PII a,PII b){return a.y/a.x>b.y/b.x;});//相当于结构体排序,排序规则是按照单价最高从大到小
    
    int res=0;
    for(PII N:arr)if(t>=N.x)res+=N.y,t-=N.x;
    
    cout<<res;
    
    return 0;
}

检测数据
在这里插入图片描述
这里我们看起来是对了,但究其根本,我们完全不清除这样的贪心是否是正确的决策

继续通过更多的数据
[算法课]实验课全题目详解_第49张图片
我们会发现,所有的测试点都出问题了,可见我们的策略是有错误的

我们进行第二种方法

从N个物品里面选择K个物品,并且有着条件限制且只能拿一次,是非常明显的01背包问题
我们复习一次,01背包问题的思考方式
即:
1.正常存储我们所需要读入的数据
2.确定我们的状态表示
3.确定我们的限制条件
4.确定我们的状态转移方程
5.输出答案

以这道题为例,我们首先开始明确参数:

1.T     则表明我们一共有T的时间
2.M     这表明我们一共有M的草药
3.TIME  这表明我们每颗草药分别需要消耗TIME[i] 的时间
4.VALUE 这表明我们每颗草药分别可以获得VALUE[i]的价值

我们思考每个可能存在的状态时,可以表达为
arr[i][j] 即该状态表明,
参数i->现在我们开始判定第i个草药是否需要摘取
参数j->现在我们一共还剩下j的时间可以采药
f[i][j]->表明这个状态下我们一共有 f[i][j]的价值

集合
所以我们相当于一直在考虑 不同时间长度 不同选择草药的方案的集合 (考虑了前i个物品 重量为j 的方案的集合)

状态转移方程
我们每次摘取,都要考虑摘取这一次是否能使得我们获得的价值更高
即我们是否该选择第i个

获取答案
我们可以检查在不同背包的容量情况下,我们的价值最高的是哪一个

以下是我们整个的逻辑过程
[算法课]实验课全题目详解_第50张图片

#include

using namespace std;
const int N=1e3+10;
int arr[N][N*10];
int Time[N],value[N];

int main()
{
    int t,m;
    cin>>t>>m;
    
    for(int i=1;i<=m;i++)cin>>Time[i]>>value[i];//读取数据
    
    for(int i=1;i<=m;i++)    //状态表示
        for(int j=1;j<=t;j++)
            if(j>=Time[i])arr[i][j]=max(arr[i-1][j],arr[i-1][j-Time[i]]+value[i]);//如果剩余有时间,我们就摘取
            else arr[i][j]=arr[i-1][j];//否则就不摘
    
    int res=-1;    
    for(int i=1;i<=t;i++)res=max(res,arr[m][i]);//找到不同剩余时间下的最大价值
    cout<<res;
    
    return 0;
}

在这里插入图片描述
空间优化

#include

using namespace std;
const int N=1e3+10;
int arr[N*10];
int Time[N],value[N];

int main()
{
    int t,m;
    cin>>t>>m;
    
    for(int i=1;i<=m;i++)cin>>Time[i]>>value[i];
    
    for(int i=1;i<=m;i++)    
        for(int j=t;j>=Time[i];j--)
            arr[j]=max(arr[j],arr[j-Time[i]]+value[i]);//我们可以依照上面的逻辑直接把二维优化成一维
    
    cout<<arr[t];//因为优化成了一维,所有数据只有不变和变大,我们直接输出最终值即可
    
    return 0;
}

在这里插入图片描述

第二次优化空间

#include

using namespace std;
const int N=1e4+10;
int arr[N];

int main()
{
    int t,m;
    cin>>t>>m;
    
    int Time,value;
    for(int i=1;i<=m;i++)
    {
        cin>>Time>>value;
        for(int j=t;j>=Time;j--)
            arr[j]=max(arr[j],arr[j-Time]+value);//直接把F降维成一维,Time,Value省去存储空间
    }

    cout<<arr[t];
    
    return 0;
}

在这里插入图片描述

我们这个时候可以发现的问题在于
DP的概念和记忆化搜索很相似

第二题 货币系统

要解决这道题我们需要在01背包问题的基础上拓展完全背包问题
区别在于完全背包问题的是无限可取的,而01背包问题的状态只有取和不取

我们首先复习一下完全背包问题
[算法课]实验课全题目详解_第51张图片
完全背包问题思考逻辑
[算法课]实验课全题目详解_第52张图片
整体逻辑

#include

using namespace std;
const int N=1e3+10;
int f[N][N];//状态表示 f[i][j] 表明 前i个物品 体积为j 价值为f[i][j]
int v[N],w[N];//v 重量 w价值

int main()
{
    int n,m;//n数量 m背包容量
    cin>>n>>m;
    
    for(int i=1;i<=n;i++)cin>>v[i]>>w[i];//读入每个物品的价值和体积
    
    for(int i=1;i<=n;i++)
        for(int j=0;j<=m;j++)//为什么从0为起始 因为重量可以表示从0到m
            for(int k=0;k*v[i]<=j;k++)//选择第i个物品的数量,只要不超出总容量,显然你就可以一直存放
                f[i][j]=max(f[i-1][j],f[i][j-k*v[i]]+k*w[i]);//当我们抉择第i个是否选择时,这里对比的是可以选择多少第i个物品,或者不选择,使得自身保持不变
    
    int res=0;
    for(int i=1;i<=m;i++)res=max(res,f[n][i]);//在各个选择完数量 但是重量不一致的方案中 挑去价值最高的
    cout<<res;
    return 0;
}

优化
我们这里需要使用三重循环,复杂度显然太高

我们阐明挑选有多少个第i个物品时,f[i][j]的表示

f[i][j]   =max(f[i-1][j],f[i-1][j-v]+w,	f[i-1][j-2*v]+2*w,	f[i-1][j-3*v]+3*w,...)
f[i][j-v] =max(          f[i-1][j-v],	f[i-1][j-2*v]+w,	f[i-1][j-3*v]+2*w,	f[i-1][j-4*v]+3*w,...)
通过观察,我们可以得到 f[i][j]=f[i][j-v]+w;

于是我们可以得到新的状态转移方程

    for(int i=1;i<=n;i++)
        for(int j=0;j<=m;j++)
        	if(j>=v[i])
           		f[i][j]=max(f[i-1][j],f[i][j-v[i]]);
           	else f[i][j]=f[i-1][j];

我们提交之后发现这样的逻辑是可行的

[算法课]实验课全题目详解_第53张图片

现在我们优化空间
降低不必要的空间复杂度

这里降低的依据只有一条 就是等价代换

#include

using namespace std;
const int N=1e3+10;
int v[N],w[N];
int f[N];

int main()
{
    int n,m;
    cin>>n>>m;
    
    for(int i=1;i<=n;i++)cin>>v[i]>>w[i];
    
    for(int i=1;i<=n;i++)
        for(int j=v[i];j<=m;j++)//f[i]条件提到循环中 这种情况下更新就只剩一个
            f[j]=max(f[j],f[j-v[i]]+w[i]);//降维
            
    cout<<f[m];//一维只有不增,和增,输出最后一个
    
    return 0;
}

我们再次提交,返现可行
[算法课]实验课全题目详解_第54张图片
有了完全背包问题的概念之后,我们来查看问题 货币系统
[算法课]实验课全题目详解_第55张图片

这里值得注意的是,我们求得是方案数量,而非最大价值
我们现在朴素思路走一遍

#include

using namespace std;
const int N=3e3+10;
typedef long long LL;//存在爆栈 所以我们使用 long long int 
LL f[N][N];//DP
LL v[N];//货币面值

int main()
{
    int n,m;
    cin>>n>>m;//n钟货币,m的面值
    
    f[0][0]=1;//初始化起点的种数为1
    
    for(int i=1;i<=n;i++)cin>>v[i];//输入不同的面值
    
    for(int i=1;i<=n;i++)//考虑前i种
        for(int j=0;j<=m;j++)//总的面值为j
            for(int k=0;k<=j;k++)//每次取k张面值为v[i]的货币
                if(k*v[i]<=j)//如果我们能拿得下
                    f[i][j]+=f[i-1][j-k*v[i]];//前i种货币且总面值为j的方案数量 累加了 前一种货币时候的所有可能的面值的情况
    
    cout<<f[n][m];//输出数量为n位置为m的方案的数量
    
    return 0;
}

依照这个逻辑,我们发现
当前的状态都是由之前的状态所决定的
我们由第一题的解答所获得的收获可以产生一个疑惑
是不是
1.当前状态由之前的状态所决定
2.拥有记忆化特性
3.拥有搜索特征
的策略即为DP?

现在我们来查看这个方式所耗费的时间
在这里插入图片描述

我们现在利用刚才推导出来的优化空间的版本进行解决

#include

using namespace std;
const int N=3e3+10;
typedef long long int LL;
LL v[N];
LL f[N];

int main()
{
    int n,m;
    cin>>n>>m;
    
    for(int i=1;i<=n;i++)cin>>v[i];
    
    f[0]=1;//初始化为1
    
    for(int i=1;i<=n;i++)
        for(int j=v[i];j<=m;j++)
            f[j]+=f[j-v[i]];//直接增加
            
    cout<<f[m];
    
    return 0;
}


在这里插入图片描述

我们继续优化一个空间 v

#include

using namespace std;
const int N=3e3+10;
typedef long long int LL;
LL f[N];

int main()
{
    int n,m;
    cin>>n>>m;
    
    f[0]=1;//初始化为1
    
    int v;
    for(int i=1;i<=n;i++)
    {
        cin>>v;//因为每次都是v[i],我们直接去掉整个v数组,加入临时变量
        for(int j=v;j<=m;j++)
            f[j]+=f[j-v];
    }
    
    cout<<f[m];
    
    return 0;
}


在这里插入图片描述

现在的时间就被压缩到了11MS

第三题 庆功会*

处理这个问题之前,我们先了解一下多重背包问题
[算法课]实验课全题目详解_第56张图片

你可以把它理解为01背包问题或者完全背包问题的变体
唯一的区别就是在两者基础上加入了一个东西能放多少个,所以我要加入一个循环

#include

using namespace std;

const int N=1e2+10;
int f[N][N];
int v[N],w[N],s[N];

int main()
{
    int n,m;
    cin>>n>>m;
        
    for(int i=1;i<=n;i++)cin>>v[i]>>w[i]>>s[i];//s 一个物品最多能获取多少个
    
    for(int i=1;i<=n;i++)
        for(int j=0;j<=m;j++)
            for(int k=0;k*v[i]<=j&&k<=s[i];k++)//我们挑取K个第i个物品,且总重量不能大于s[i]
                f[i][j]=max(f[i][j],f[i-1][j-k*v[i]]+k*w[i]);//我们挑取K个第i个物品,可以挑选多少个


    
    int res=1;
    for(int i=0;i<=m;i++)res=max(res,f[n][i]);//在同样挑选完毕 但是重量不同的方案中得到最佳答案
    cout<<res;
    
    return 0;
}

在这里插入图片描述

优化
优化逻辑参照我前两题写的

Q:为什么这里的 注意点1 需要从大到小循环?
A:因为我们压缩的了代码,将二维降低到了一维,f[i, j] = max(f[i - 1, j], f[i - 1, j - v] + w,那么只能从大到小循环,否则计算j时,j - v会先被计算,那么其实算的就是f[i, j] = max(f[i - 1, j], f[i], j - v] + w了,不等价。

#include

using namespace std;

const int N=1e2+10;
int f[N];

int main()
{
    int n,m;
    cin>>n>>m;
    
    int v,w,s;
    for(int i=1;i<=n;i++)
    {
        cin>>v>>w>>s;
        for(int j=m;j>=0;j--)//注意点1
            for(int k=0;k*v<=j&&k<=s;k++) 
                f[j]=max(f[j],f[j-k*v]+k*w);
    }


    
    cout<<f[m];
    
    return 0;
}

在这里插入图片描述

现在我们来查看庆功会这道题
[算法课]实验课全题目详解_第57张图片
很明显是多重背包的裸题

我们直接利用上面优化出来的方式解决这道题目

#include

using namespace std;

const int N = 6e3+10;
int f[N];

int main()
{
    int n,m;
    cin>>n>>m;
    
    int v,w,s;
    for(int i=0;i<n;i++)
        {
            cin>>v>>w>>s;
            for(int j=m;j>=0;j--)
                for(int k=0;k*v<=j&&k<=s;k++)
                    f[j]=max(f[j],f[j-k*v]+k*w);
        }
    
    cout<<f[m];
    
    return 0;
}

在这里插入图片描述

总结

我希望对本次的所有题目做一个总结
现在我们们列出所有题目的以及对应的题型

	题目									具体题型				具体做法
第一部分 递归
	第一题 数的计算						动态规划				利用数据的规律推导出后面的数据
	第二题 选数							深度优先搜索			n个数据当中选择K个数据,使用DFS暴力搜索
	第三题 滑雪 *							深度优先搜索 && 回溯	使用DFS的同时,检查当前的高度是不是一定大于后面的
第二部分 分治法
	第一题 烦恼的高考志愿					二分					通过二分找出与实际成绩差值的最小值,累加
	第二题 平面上的最接近点对				图论||暴力+勾股定理	通过勾股定理计算出距离
	第三题 砍树 *							二分					通过二分找出找出适合的机器的高度,以最高树的一半2
第三部分 蛮力法
	第一题 完美情侣						暴力					暴力比对数据,if a+b=c then ans++
	第二题 化简							暴力					检查是否是素数
	第三题 四方定理*						暴力					提前用数组判定平方数,然后再用三重循环卡常
第四部分 回溯法
	第一题 单词方阵						深度优先搜索			使用八个方向进行搜索
	第二题 小偷问题						回溯					回溯顺序检查
	第三题 小猫爬山*						深度优先搜索 			重要的是何时生成新的车
第五部分 贪心法
	第一题 陶陶摘苹果(升级版)		 		排序					利用贪心原则,有限摘取符合高度要求的最小值
	第二题 铺地毯							模拟					确定范围,反序查询
	第三题 局域网*						最小生成树			prim或者克洛斯卡尔算法,找到总长减去最小生成树
第六部分 动态规划
	第一题 采药							01背包问题			裸题,确认f[i][j]整体关系
	第二题 货币系统						完全背包问题	 		裸题,加一重循环,找出f[i][j]和f[i][j-v]+w的关系
	第三题 庆功会*						多重背包问题			裸题,加一重关系,确认其与完全背包和多重背包的关系

我们会发现,题目实际上的难度并不高,但是广度却涉猎相对较广,且需要细加分析。
我暂时针对做题总结以下三点:
1.出现了非常多的DFS题目,DFS的方式和生成概念基础且重要,因为递归和回溯还有DFS再很多很多算法都涉及,过于重要了,所以我个人需要好好掌握。
2.很多题目的题型相同,但具体的实现方式和解决的问题都大不相同,这事实上是一个需要自己逐渐熟悉的过程,这里要表明的态度是,我们很需要用简单的方法做,再优化到其他写法,一步到位对我个人是空中楼阁,美好却危险又不可取。
3.很多题目的题型不相同,广度相对较广,但是用法都非常基础,但事实上我们需要知道对应做题的普遍思路和模板,比方说 二分,N取K类型的DFS,涉及图的DFS,GCD,欧拉筛,最小生成树,背包问题,他们在这里的应用都有普遍的特征 不难,但是需要掌握相关的知识,空想的结果很可能就是只过了样例,或者被迫抄袭,所以我们可能需要见多识广。

你可能感兴趣的:(算法课)