算法设计分析
绪论
对称二叉树
题目
代码
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<string>
using namespace std;
int main(){
string s;
cin>>s;
int i;
for(i=1;i<s.length();i+=2)//定义一个循环,从字符串的第二个字符开始,每次递增2,遍历字符串的奇数索引位置。
{
if(s[i]!='#' && s[i+1]=='#' || s[i+1]!='#' && s[i]=='#' ||s[i+1]=='\0')
{
//判断是否对称,如果有不满足条件的,即为不对称,输出“NO”并退出
printf("No\n");
break;
}
}
if(i>=s.length())printf("Yes\n");//如果没有中途退出,则说明都满足条件,该二叉树对称输出“Yes”
return 0;
}
- for(i=1;i<s.length();i+=2)
- 定义一个循环,从字符串的第二个字符开始,每次递增2,遍历字符串的奇数索引位置。
- if(s[i]!=‘#’ && s[i+1]==’ #’ || s[i+1]!=‘#’ && s[i] ==‘#’ ||s[i+1] ==‘\0’)
- 判断当前位置和下一个位置的字符是否满足对称条件。如果当前位置不是空而下一个是空,或者当前位置是空而下一个不是空,或者下一个位置是字符串的结尾(这在本例中不会发生,因为i+1永远不会超过字符串长度),则说明二叉树不对称。
递归算法
分治法
回溯法
骨牌铺方格
题目
代码
#include <iostream>
using namespace std;
typedef long long LL;
LL dp[55];//防止越界
int main()
{
int n;
dp[1] = 1;
dp[2] = 2;
//打表 + 状态转移方程
for (int i = 3;i <= 55;i++) dp[i] = dp[i - 1] + dp[i - 2];
while (cin >> n)
{
cout << dp[n] << endl;
}
return 0;
}
- for (int i = 3; i <= 55; i++) dp[i] = dp[i - 1] + dp[i - 2];
- 循环从3到55,使用动态规划的状态转移方程来填充数组dp。状态转移方程基于以下逻辑:对于宽度为i的长方形,铺放方案数等于宽度为i-1的方案数加上宽度为i-2的方案数。
- 这是因为你可以在第一个位置竖着放置一个骨牌,然后剩下的宽度为i-1,或者你可以在第一个位置横着放置一个骨牌,然后剩下的宽度为i-2。(体现了回溯的思想)
🌟求解图的m着色问题(递归回溯法)
题目
给定无向连通图G和m种不同的颜色。用这些颜色为图G的各顶点着色,每个顶点着一种颜色。如果有一种着色法使G中每条边的两个顶点着不同颜色,则称这个图是m可着色的。图的m着色问题是对于给定图G和m种颜色,找出所有不同的着色法。
代码
void dfs(int i) //求解图的m着色问题
{
if (i>n){
count++;
}else{
for (int j=1;j<=m;j++){
x[i]=j;
if (Same(i))
dfs(i+1);
x[i]=0;
}
}
}
- 函数void dfs(int i):
- 作用:递归地尝试为图中的每个顶点着色,并判断是否满足条件。
- 实现:如果当前顶点编号i大于顶点总数n,则找到一个有效的着色方案,count加1。
- 否则,遍历1到m的颜色,尝试为当前顶点i着色。
- 如果当前颜色与相邻顶点颜色不冲突(调用Same(i),注意其判断颜色相同返回false,颜色不同返回true),则继续递归为下一个顶点着色。
- 每次尝试后,将当前顶点的颜色重置为0,以便下次尝试。
- 递归的dfs(i+1)与if (i>n) count++; 形成呼应,当递归地为下一编号的节点着色到超出了顶点的总数,说明已经着色完毕,着色方法数+1
分支界限法
求解n皇后问题(递归回溯法)
题目
在n×n的方格棋盘上,放置n个皇后,要求每个皇后不同行、不同列、不同左右对角线。如下图所示是6皇后问题的一个解。
代码
void queen(int i,int n) //放置1~i的皇后
{
if (i>n)
dispasolution(n); //所有皇后放置结束
else{
for (int j=1;j<=n;j++) //在第i行上试探每一个列j
if (place(i,j)) //在第i行上找到一个合适位置(i,j)
{
q[i]=j;
queen(i+1,n);
}
}
}
-
函数bool place(int i, int j):
-
作用:测试(i, j)位置能否摆放皇后,即检查第i行第j列是否安全。
-
实现:从第1行到第i-1行,检查是否有皇后在同一列或对角线上。使用abs函数
计算对角线距离。 -
while (k < i)
- 这是一个while循环,条件是k小于i。变量k从1开始,表示已经放置了皇后的行,从第1行到第i-1行。
-
if ((q[k] == j) || (abs(q[k] - j) == abs(i - k)))
- 这是一个if语句,用于检查两种情况:
- q[k] == j
- 如果在第k行已经放置了一个皇后,并且它位于第j列,那么在第i行第j列放置皇后就不安全,因为它们在同一列。
- abs(q[k] - j) == abs(i - k)
- 使用abs函数计算第k行的皇后与当前考虑放置的皇后之间的列距离和行距离。如果这两个距离相等,说明它们在同一斜线上,因此放置皇后也是不安全的。
- q[k] == j
- 这是一个if语句,用于检查两种情况:
-
return false;
- 如果上述任何一种情况成立,place函数将返回false,表示在第i行第j列放置皇后是不安全的。
-
k++;
- 如果当前的k对应的行没有发现冲突,循环变量k自增1,继续检查下一行。
-
如果循环正常结束,没有发现任何冲突,place函数将返回true,表示在第i行第j列放置皇后是安全的。
-
这个检查过程是N皇后问题中的关键部分,确保了每次放置皇后时,都不会违反互不攻击的规则。
-
-
函数void queen(int i, int n):
- 作用:递归地放置1到i的皇后。
- 实现:如果i大于n,则表示所有皇后都已放置,调用dispasolution函数输出解。
- 否则,遍历第i行的所有列,使用place函数检查每个位置是否安全。
- 如果找到一个安全的位置,则将皇后放置在该位置,并递归地放置下一个皇后。
-
这个程序使用了递归回溯算法来解决N皇后问题。它从第1行开始,逐行尝试放置皇后,对于每一行,它尝试在所有列中找到一个安全的位置。如果找到一个安全的位置,就将皇后放置在那里,并递归地放置下一行的皇后。如果当前行的所有列都不安全,就回溯到上一行,移动皇后到下一个安全的位置,然后继续尝试(queen中的for循环)。这个过程一直进行,直到找到所有皇后的放置方案或所有可能都被尝试过。每次找到一个完整的解时,都会调用dispasolution函数来输出这个解。
动态规划法
多段图最短路径问题(C++)
题目
设图G=(V,E)是一个带权有向连通图,如果把顶点集合V划分成k个互不相交的子集Vi(2⩽k⩽n,1⩽i⩽k),使得E中的任何一条边(u,v),必有u∈Vi,v∈Vi+m(1⩽i<k,1<i+m⩽k),则称图G 为多段图,称s∈V1为源点,t∈Vk为终点。多段图的最短路径问题是求从源点到终点的最小代价路径。
由于多段图将顶点划分为k个互不相交的子集,所以,多段图划分为k段,每一段包含顶点的一个子集。将多段图的顶点按照段的顺序进行编号,同一段内顶点的相互顺序无关紧要。假设图中的顶点个数为n,则源点s 的编号为0,终点t的编号为n−1,并且,对图中的任何一条边(u,v),顶点u 的编号小于顶点v的编号。下图是一个含有10 个顶点的多段图。
要求:1必须采用动态规定法编写能求解多段图的最短路径问题的函数和一个能输出最短路径输出的函数。
编写函数所需的数据类型及数据结构在裁判样例程序中已经给出,编写MakingPath函数和displayResult函数是必须沿用这些关键数据类型和数据结构。请仔细阅读裁判样例程序中的注释,理解数据的组织方式。
函数接口定义:
在这里描述函数接口。例如:
void MakingPath(Graphic *g,Table *t);
void DisplayResult(Table *t)
第一个函数时对最短路径进行决策的函数,第二个函数时输出决策结果的函数。其中 参数Graph *g 是指向描述图的矩阵存储结构的参数, Table *t 指向存储存路径决策过程表指针变量。
裁判测试程序样例:
#include<iostream>
#define INFINITY 255 //INFINITY标志,两点之间无边,即不连通
using namespace std;
class Graphic{
public:
int vnum; //图的顶点数
int arcnum; //图的边数
int *arc=NULL; //图中顶点连接关系数组
Graphic(){} //Graphic类的无参构造函数
Graphic(int vertexNum,int edgeNum){ //Graphic类的有参构造函数
vnum=vertexNum;
arcnum=edgeNum;
arc=new int[vertexNum*vertexNum];
}
/*** 设置顶点x和y之间关联边的权值的函数 ***/
void setArc(int x,int y,int value)
{
arc[vnum*x+y]=value;
}
/*** 获得顶点x和y之间关联边的权值的函数 ***/
int getArc(int x,int y)
{
return arc[vnum*x+y];
}
~Graphic(){ } //Graphic类的析构函数
};
class Table{
public:
int vertexNum; //决策表中的数组长度,也是图中的顶点数
int *cost=NULL; //决策过程中存储最短路径长度数组
int *path=NULL; //决策过程中顶点状态转移的数组
Table(){} //Table类的无参构造函数
Table(int vertexNum){ //Table类的有参构造函数
this->vertexNum=vertexNum;
cost=new int[vertexNum];
path=new int[vertexNum];
for(int i=0;i<vertexNum;i++){
cost[i]=INFINITY; //将最短路径表中的元素全部置为无穷大
path[i]=-1; //将节点状态转移表中的元素置为-1
}
}
~Table(){}//Talbe类的析构函数
};
class MultistageDecision {
public:
void creatGraph(Graphic g)
{
/**以下将图初始化为零图,即图中的节点均为孤立点**/
for (int i = 0; i < g.vnum; i++)
for (int j=0; j < g.vnum; j++)
g.setArc(i,j,INFINITY);
/*以下循环构造两点之间的连接,并存关联边的距离*/
int i=0,j=0,w=0;
for (int k = 0; k < g.arcnum; k++){
cin>>i>>j>>w;
g.setArc(i,j,w); //将关联边长度存入图的矩阵i行j列
}
}
/********你编写的两个函数将嵌入在这里*******/
};
int main() {
int vertexNum=0,arcNum=0;
cin>>vertexNum>>arcNum;
if(vertexNum>0&&arcNum>=0){
Graphic g(vertexNum,arcNum);
Table t(vertexNum);
MultistageDecision md;
md.creatGraph(g);
md.MakingPath(g,t);
md.DisplayResult(t);
}
else
{
cout<<"输入的顶点数小于1,或者边数为小于0,无法构成图!";
}
return 0;
}
Graphic 类
- 表示一个带权有向图,使用邻接矩阵存储。
- 成员变量:
- vnum:顶点数。
- arcnum:边数。
- arc:一个二维数组,存储顶点间的连接关系和权重。
- 成员函数:
- 构造函数:初始化图对象。
- setArc:设置两个顶点之间的边的权重。
- getArc:获取两个顶点之间边的权重。
Table 类
- 用于存储最短路径算法中的决策信息。
- 成员变量:
- vertexNum:顶点数。
- cost:存储从源点到每个顶点的最短路径长度。
- path:存储最短路径的前驱节点,用于路径重建。
- 成员函数:
- 构造函数:初始化决策表对象,并将所有顶点的最短路径长度初始化为无穷大,前驱节点初始化为-1。
MultistageDecision 类
- 包含求解多段图最短路径问题的算法。
- 成员函数:
- creatGraph:根据输入初始化图的邻接矩阵。
- MakingPath:实现动态规划算法求解最短路径问题。
- DisplayResult:输出最短路径的长度和路径。
代码
void MakingPath(Graphic& g, Table& t) {
int i, j;
int n = g.vnum;
// 顶点0为源点
t.cost[0] = 0;
for (j = 0; j < n; j++) {
for (i = j - 1; i >= 0; i--) {
if (t.cost[i] + g.getArc(i, j) < t.cost[j]) {
t.cost[j] = t.cost[i] + g.getArc(i, j); // 更新节点的成本
t.path[j] = i; // 更新节点的路径
}
}
}
}
void DisplayResult(Table& t) {
int num[100];
int temp = 1;
cout << t.cost[t.vertexNum-1]<<" [";
for (int i = t.vertexNum-1; t.path[i] >= 0;) { // 遍历路径数组以找到最短路径
num[temp++]=t.path[i]; // 打印最短路径中的节点
i = t.path[i]; // 更新索引以遍历路径数组
}
for (int i = temp - 1; i > 0; i--)
cout << num[i] << ",";
cout << t.vertexNum - 1 <<"]"<< endl;
}
MakingPath 函数
- 这个函数的目的是计算从源点到图中所有其他顶点的最短路径。它使用动态规划的思想,通过迭代的方式逐步更新每个顶点的最短路径成本。
- 参数Graphic& g:引用传递一个Graphic对象,表示图的结构。
- 参数Table& t:引用传递一个Table对象,用于存储最短路径的计算结果。
- 函数逻辑:
- 初始化源点(顶点0)到自身的成本为0。
- 使用两个嵌套循环遍历所有顶点对:
- 外层循环for (j = 0; j < n; j++):遍历所有顶点,作为当前考虑的终点。
- 内层循环for (i = j - 1; i >= 0; i–):从当前顶点的前一个顶点开始,向前遍历到源点。
- 在内层循环中,使用条件if (t.cost[i] + g.getArc(i, j) < t.cost[j])检查通过顶点i到达顶点j的路径是否比当前已知的路径更短。如果是,则更新顶点j的最短路径成本t.cost[j]和前驱节点t.path[j]。
DisplayResult 函数
- 这个函数用于显示从源点到终点的最短路径及其长度。
- 参数Table& t:引用传递一个Table对象,包含最短路径的计算结果。
- 函数逻辑:
- 首先输出从源点到终点的最短路径长度t.cost[t.vertexNum-1]。
- 使用一个数组num存储最短路径上的顶点编号。
- 构建并输出最短路径:
- " [":输出一个起始的方括号。
- for (int i = t.vertexNum-1; t.path[i] >= 0;):使用一个循环从终点开始逆向追踪路径。循环条件是当前顶点的前驱节点存在(即t.path[i] >=0)。
- num[temp++] = t.path[i]:将当前顶点的前驱节点编号存储到num数组中,并递增temp。
- i = t.path[i]:更新当前顶点为前一个顶点,以便下一次循环继续逆向追踪。
- 逆序输出路径上的顶点编号:
- for (int i = temp - 1; i > 0; i–):从num数组的最后一个元素开始向前遍历,逆序输出顶点编号。
- cout << num[i] << “,”:输出顶点编号,并在其后添加逗号作为分隔符。
- 输出源点编号并结束路径输出:
- cout << t.vertexNum - 1 << “]”:输出源点的编号(即顶点数组的最后一个元素),并添加一个结束的方括号。
数字三角形问题
题目
代码
#include<bits/stdc++.h>
using namespace std;
const int Max=101;
int n;
int dp[Max][Max]; //dp[i][j]表示点(i,j)的上的数字
int maxSum(int n) //maxSum(n)表示层数为 n 的三角形,从顶到底的数字总和的最大值
{
for(int i=n-1;i>=1;i--) //从底开始
{
for(int j=1;j<=i;j++) //这里的dp[i][j]表示从底到点(i,j)的最大数字和
{
dp[i][j]=max(dp[i+1][j],dp[i+1][j+1])+dp[i][j]; //从下往上到达点(i,j)有两条路:(1)从点(i+1,j)到达;(2)从点(i+1,j+1)到达。选数字较大者
}
}
return dp[1][1]; //从底到点(1,1)的最大数字和,即为所求值
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=i;j++)
cin>>dp[i][j];
}
cout<<maxSum(n)<<endl;
return 0;
}
-
int dp[Max][Max];
- 定义一个二维数组dp,用于存储动态规划过程中的中间结果。dp[i][j]表示到达三角形的第i行第j列的点的最大数字总和。
-
函数int maxSum(int n):
- 使用两个嵌套循环,从三角形的底部开始向上计算每一点的最大数字总和。
- 外层循环 for(int i = n - 1; i >= 1; i–):从三角形的最后一行开始向上遍历,直到第一行。
- 内层循环 for(int j = 1; j <= i; j++):在每一行中,从该行的第一个数字遍历到最后一个数字。
- 动态规划状态转移方程:
- 在内层循环中,对于每个点(i, j),计算从它下方的两个点(i + 1, j)和(i + 1, j + 1)到达的最大总和。这里使用max函数来选择两者中较大的一个值。
- 更新当前点的dp值:dp[i][j] = max(dp[i + 1][j], dp[i + 1][j + 1]) + dp[i][j];
- 这里dp[i][j]是当前点的值加上从它下方两个点到达的最大路径和。
- 最后返回dp[1][1],即到达三角形顶部的最大数字总和。
最大连续子序列和
题目
代码
/*最大子列和问题*/
#include <stdio.h>
#include <stdlib.h>
#define N 100000
int main()
{
int i,n;
int a[N];
int sum=0;//当前总和
int Maxsum=0;//最大的总和
scanf("%d",&n);
for(i=0;i<n;i++)
scanf("%d",&a[i]);
for(i=0;i<n;i++)
{
sum+=a[i];
if(sum>Maxsum)
Maxsum=sum;
else if(sum<0)
sum=0;
}
printf("%d\n",Maxsum);
return 0;
}
这个程序使用了Kadane算法来解决最大子数组和问题。Kadane算法的核心思想是,如果当前子数组的和变为负数,则抛弃之前的所有数字,从当前数字开始新的子数组,因为任何包含负数的子数组的和都不会是最大子数组和的候选者。这种方法可以有效地在一次遍历中找到最大子数组和。
- 第二个for循环:for(i = 0; i < n; i++)
- 再次遍历数组,计算最大子数组和。
- 循环内部:
- sum += a[i];
- 将当前元素的值加到sum上。
- if(sum > Maxsum) Maxsum = sum;
- 如果当前子数组的和大于已记录的最大和,则更新Maxsum最大子数组和。
- else if(sum < 0) sum=0;
- 如果当前子数组的和小于0,则重置sum为0,
- sum += a[i];
最长连续递增子序列
题目
代码
#include<stdio.h> // 包含标准输入输出库函数,用于程序中的输入输出操作。
#define max 100000 // 定义一个宏max,用于设定数组可能的最大长度为100000。
typedef struct node { // 定义一个结构体node,它包含一个整型数组a。
int a[max];
}list; // 为结构体node定义一个别名list,用于后续使用。
int main() { // 主函数开始。
int n; // 定义一个整型变量n,用于存储数组的长度。
list p; // 定义一个list类型的变量p,用来存储数组。
scanf("%d", &n); // 从标准输入读取一个整数,存储在变量n中,表示数组的长度。
int i = 0; // 定义并初始化一个循环计数器i为0。
// 循环读取n个整数,存储到结构体p中的数组a里。
for (i = 0; i < n; i++) {
scanf("%d", &p.a[i]);
}
// 初始化两个变量maxp和maxlen,用于记录最长递增子序列的起始位置和长度。
int maxp = 0, maxlen = 1;
// 初始化两个变量len和position,用于记录遍历过程中递增子序列的当前长度和起始位置。
int len = 1, position = 0;
// 从第二个元素开始遍历数组,寻找最长递增子序列。
for (i = 1; i < n; i++) {
// 如果当前元素不大于前一个元素,说明递增子序列结束。
if (p.a[i - 1] >= p.a[i]) {
// 如果当前记录的递增子序列长度大于已知的最长长度,则更新最长子序列的信息。
if (len > maxlen) {
position = i;
maxlen = len;
maxp = position - maxlen; // 计算最长递增子序列的起始位置。
}
len = 1; // 重置当前递增子序列的长度。
} else {
// 如果当前元素大于前一个元素,递增子序列继续。
len++;
}
}
// 循环结束后,再次检查最后一个递增子序列是否是最长的。
if (len > maxlen) {
maxlen = len;
maxp = i - maxlen; // 更新最长递增子序列的起始位置。
}
// 打印最长递增子序列。
for (int j = maxp; j < maxp + maxlen; j++) {
if (j == maxp) {
printf("%d", p.a[j]); // 打印序列的第一个元素,后面不添加空格。
} else {
printf(" %d", p.a[j]); // 打印序列的其余元素,后面添加空格分隔。
}
}
return 0; // 主函数结束,返回0表示程序正常退出。
}
- int maxp = 0, maxlen = 1;
- 初始化两个变量maxp和maxlen,用于记录最长递增子序列的起始位置和长度。
- int len = 1, position = 0;
- 初始化两个变量len和position,用于记录遍历过程中递增子序列的当前长度和起始位置。
- for (i = 1; i < n; i++) 从第二个元素开始遍历数组,寻找最长递增子序列
- if (p.a[i - 1] >= p.a[i])
-
如果当前元素不大于前一个元素,说明递增子序列结束。
- if (len > maxlen) position = i;maxlen = len;
- 如果当前记录的递增子序列长度大于已知的最长长度,则更新最长子序列的信息。
- maxp = position - maxlen; // 计算最长递增子序列的起始位置。
- if (len > maxlen) position = i;maxlen = len;
-
如果当前元素大于前一个元素,递增子序列继续
-
01背包问题(ppt)
贪心法
活动选择问题
题目
思路:
- 这个题类似无重叠区间 就是最后求出的区间为互不重合的区间(不包括边界)
- 那么我们在处理数据的时候是将每两个数存在一起,再用一个大的容器将其存起来
- 处理数据我们按每组数据的结束时间升序处理,
- 紧接着求出第一个区间右边界,跟下一组的左边界进行比较,如果小于等于其左边界,那么说明区间不覆盖了,我们就需要更新右边界 最后统计其个数
代码
#include<bits/stdc++.h>
using namespace std;
//处理每组的数据让其第二个值大于前一个值
static bool cmp(const vector<int>& a, const vector<int>& b){
return a[1] < b[1];
}
int main(){
int N;
int count = 1;//表示取出的第一个区间第一组数据
vector<vector<int> >v;//注意这里的空格 一定要有 这是大容器存每组的数据 。声明一个二维向量v,用于存储所有活动的时间信息。
vector<int>v1;//存一组数据,声明一个一维向量v1,用于临时存储每对活动时间。
cin >> N;
for(int i = 0; i < N; i++){
// 循环读取每个活动的开始和结束时间。
for(int j = 0; j < 2; j++){
int temp;
cin >> temp; // 读取一个整数到temp。
v1.push_back(temp); // 将temp添加到v1中。
}
v.push_back(v1); // 将v1中的活动时间信息复制到v中。
v1.clear(); // 清空v1,为下一组活动时间做准备。
}
sort(v.begin(),v.end(),cmp);
int num = v[0][1];//第一组数据的右边界
for(int i = 1; i < N; i++){
// 从第二个活动开始遍历,寻找不重叠的活动。
if(num <= v[i][0]){ // 如果当前活动的开始时间大于或等于num(最晚结束时间),则它们不重叠。
num = v[i][1]; // 更新最晚结束时间为当前活动的结束时间。
count++; // 增加兼容活动集的计数。
}
}
cout << count;
}
- static bool cmp(const vector& a, const vector& b){ return a[1] < b[1]; }
- 定义了一个比较函数cmp,用于比较两个活动结束时间的大小。这个函数用于sort函数中的自定义排序,如果a的结束时间小于b的结束时间,则返回true,使得a排在b前面。
- vector<vector >v;
- 定义一个二维向量v,用来存储所有活动的开始时间和结束时间。
- vectorv1;
- 定义一个一维向量v1,用来临时存储每组数据。
- 使用两个嵌套循环读取每个活动的开始和结束时间,并存储到v中:
- 外层循环for(int i = 0; i < N; i++){遍历活动。
- 内层循环for(int j = 0; j < 2; j++){读取每个活动的两个时间。
for(int i = 0; i < N; i++){
// 循环读取每个活动的开始和结束时间。
for(int j = 0; j < 2; j++){
int temp;
cin >> temp; // 读取一个整数到temp。
v1.push_back(temp); // 将temp添加到v1中。
}
v.push_back(v1); // 将v1中的活动时间信息复制到v中。
v1.clear(); // 清空v1,为下一组活动时间做准备。
}
- sort(v.begin(),v.end(),cmp);
- 使用sort函数和自定义的比较函数cmp对所有活动按照结束时间进行排序。
- int num = v[0][1];
- 初始化num为第一个活动的结束时间,这是当前考虑的最后一个活动的结束时间。
- 一个for循环,从第二个活动开始遍历所有活动:
- 如果当前活动的开始时间大于或等于num,说明它与之前选择的活动不重叠,可以加入兼容活动集。
- 更新num为当前活动的结束时间,并将count加1。
for(int i = 1; i < N; i++){
// 从第二个活动开始遍历,寻找不重叠的活动。
if(num <= v[i][0]){ // 如果当前活动的开始时间大于或等于num(最晚结束时间),则它们不重叠。
num = v[i][1]; // 更新最晚结束时间为当前活动的结束时间。
count++; // 增加兼容活动集的计数。
}
}
求解田忌赛马问题(贪心法)
题目
齐威王与大将田忌赛马。双方约定每人各出300匹马,并且在上、中、下三个等级中各选一匹进行比赛,由于齐威王每个等级的马都比田忌的马略强,比赛的结果可想而知。现在双方各n匹马,依次派出一匹马进行比赛,每一轮获胜的一方将从输的一方得到200银币,平局则不用出钱,田忌已知所有马的速度值并可以安排出场顺序,问他如何安排比赛获得的银币最多。
函数接口定义:
void solve();
裁判测试程序样例:
#include <stdio.h>
#include <algorithm>
using namespace std;
#define MAX 1001
//问题表示
int n;
int a[MAX];
int b[MAX];
//求解结果表示
int ans;
void solve();
int main()
{
scanf("%d",&n);
for (int i=0;i<n;i++)
scanf("%d",&a[i]);
for (int j=0;j<n;j++)
scanf("%d",&b[j]);
solve();
printf("%d\n",ans);
return 0;
}
/* 请在这里填写答案 */
代码
这个问题的解决方案依赖于贪心算法的思想,总是试图用最快的马赢得比赛,用最慢的马去输掉比赛。
这个策略基于以下假设:
- 田忌的最快的马匹应该用来赢得最重要的比赛,即对阵齐威王最快的马匹。
- 田忌的最慢的马匹应该用来输掉最不重要的比赛,即对阵齐威王最慢的马匹。
这种策略是贪心算法的一个应用,它在每一步选择中都做出局部最优的选择,希望这样的选择能够导致全局最优解。
void solve() {
//对两个数组进行排序,使得可以按照从快到慢的顺序选择马匹
//sort是从小带大排序
sort(a, a+n);
sort(b, b+n);
// 初始化指针和答案
//i和x是田忌的慢马和快马的指针,j和y是齐威王的慢马和快马的指针。
int i = 0, j = 0, x = n-1, y = n-1;
ans = 0;
// 当两个数组都有元素时
while(i <= x && j <= y) {
// 如果田忌的快马比齐威王的快马快
if(a[x] > b[y]) {
// 田忌赢得比赛,获得200银币
ans += 200;
// 移动快马指针,准备下一轮比较
x--;
y--;
}
// 如果田忌的快马比齐威王的快马慢
else if(a[x] < b[y]) {
// 如果田忌的慢马也比齐威王的慢马慢
if(a[i] < b[j]) {
// 田忌输掉比赛,失去200银币
ans -= 200;
}
//移动田忌的慢马指针和齐威王的快马指针
i++;
y--;
}
// 如果两匹快马的速度相同
else {
// 如果田忌的慢马比齐威王的慢马慢。
if(a[i] < b[j]) {
// 田忌输掉比赛,失去200银币
ans -= 200;
}
// 如果田忌的慢马比齐威王的慢马快。
else if(a[i] > b[j]) {
// 田忌赢得比赛,获得200银币
ans += 200;
}
// 移动两人的慢马和快马指针。
i++;
j++;
}
}
}