算法程序设计复习

目录​​​​​​​

一、上课的一些时间复杂度分析

二、上课提到的例子

第二章 分治法

7-1 二分搜索

7-2 改写二分搜索算法

7-8 整数排序

7-10 求逆序对数目

第三章 动态规划

7-12 矩阵链相乘问题

 7-16 最长公共子序列长度

7-20 0-1背包(动态规划)

7-21 子集和

第四章 贪心算法

7-26 会场安排问题

7-28 排队接水 

7-35 背包问题

7-36 最短路径-Dijkstra

第五章 回溯法

7-20 0-1背包(回溯法)

7-31 子集和问题 

7-42 n后问题

7-38 旅行售货员

 三、2022-2023学年上学期《算法设计与分析》期中小测的题目和题解

A 职业规划

B 技能提升

C 毕业要求

D 学习计划I

E 学习计划II

F 共同前行



一、上课的一些时间复杂度分析



二、上课提到的例子

第二章 分治法

7-1 二分搜索

基本功,考察二分查找,不过更多会考察变形。

 给定已按降序排好的n个元素a[0:n-1],在这n个元素中找出一特定元素x。

输入格式:

第一行为n值(n<=10^6)和查询次数m(m<=10^3);
第二行为n个整数。
接下来m个数,代表要查询的x

输出格式:

对于每一个查询的x,如果找到,输出x的下标;否则输出-1。

输入样例:
5 2 
5 4 3 2 1
3
6
输出样例:
2
-1

代码如下

#include<bits/stdc++.h>
using namespace std;

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

int two_cut_find(int num[],long long int left,long long int right,long long int x){
    if(left>right)
        return -1;
    long long int mid =(left+right)/2;
    if(num[mid]==x)
        return mid;
    if(num[mid]>x)
        return two_cut_find(num,mid+1,right,x);
    else
        return two_cut_find(num,left,mid-1,x);
    
}


int main(){
    cin>>n>>m;
    for(int i=0;i<n;i++){
        cin>>arr[i];
        
    }
    for(int j =0;j<m;j++){
        long long int x;
        cin>>x;
        cout<<two_cut_find(arr,0,n-1,x)<<endl;
    }
}

7-2 改写二分搜索算法

题目来源:《计算机算法设计与分析》,王晓东

设a[0:n-1]是已排好序的数组,请改写二分搜索算法,使得当x不在数组中时,返回小于x的最大元素位置i和大于x的最小元素位置j。当搜索元素在数组中时,i和j相同,均为x在数组中的位置。

输入格式:

输入有两行:

第一行是n值和x值;
第二行是n个不相同的整数组成的非降序序列,每个整数之间以空格分隔。

输出格式:

输出小于x的最大元素的最大下标i和大于x的最小元素的最小下标j。当搜索元素在数组中时,i和j相同。
提示:若x小于全部数值,则输出:-1 0
若x大于全部数值,则输出:n-1的值 n的值

输入样例:

在这里给出一组输入。例如:

6 5
2 4 6 8 10 12
输出样例:

在这里给出相应的输出。例如:

1 2

需要注意,小于x的最大值返回right,大于x的最小值返回left

解法一 教学代码 

#include <iostream>
using namespace std;

pair<int, int> biSearch(int x, int a[],  int left, int right)
{
  if (left > right) { //当数组中没有元素时
    pair<int, int> range(right, left);
    return range;
  }

  int middle = (left + right) / 2;
  if (a[middle] == x) {//如果找到,直接返回位置
    pair<int, int> range(middle, middle);
    return range;
  }
  else if (a[middle] > x) //如果中间的数字大于x,在数组左半部分递归查找
    return biSearch(x, a, left, middle - 1);
  else //否则在数组右半部分递归查找
    return biSearch(x, a, middle + 1, right);
}

int main()
{
  int n, x;
  cin >> n >> x;
  int a[n];
  for (int i = 0; i < n; i++)
    cin >> a[i];

  pair<int, int> range = biSearch(x, a, 0, n - 1);
  cout << range.first << " " << range.second << endl;

  return 0;

}

解法二 学生代码 

#include<bits/stdc++.h>
using namespace std;

const int N =1000000;
long long int n;
long long int x;

int arr[N];

void two_cut_num(int a[],long long int left,long int right,long int x)
{
    
	if(left>right){
		cout<<right<<" "<<left;
		return;}
	long long int mid =(left+right)/2;
	if(a[mid]==x){
		cout<<mid<<" "<<mid;
		return;}
	if(a[mid]>x)
		return two_cut_num(a,left,mid-1,x);
	else
		return two_cut_num(a,mid+1,right,x);
	
		
}

int main(){
	cin>>n>>x;
	for(int i =0;i<n;i++){
		cin>>arr[i];
	}
	two_cut_num(arr,0,n-1,x);
}

7-8 整数排序

 给定n个整数,请按照从小到大的顺序排序。

输入格式:

第一行数字n,1<=n<=100000
第二行n个整数,以一个空格分隔

输出格式:

从小到大排序后的数字,以一个空格分隔

输入样例:
5
4 2 1 3 5
输出样例:
1 2 3 4 5

考察快速排序

解法一 教学代码 

#include <iostream>
using namespace std;

void swap(int& a, int& b) {
  int tmp = a;
  a = b;
  b = tmp;
}

int partition(int a[], int left, int right) {
  int i = left + 1;
  int j = right;
  int x = a[left];
  while(true) {
    while (a[i] < x && i < right) i++;
    while (a[j] > x) j--;
    if (i >= j) break;
    swap(a[i], a[j]);
  }
  swap(a[left], a[j]);
  return j;
}

void quickSort(int a[], int left, int right) {
  if (left >= right)
    return;
  int q = partition(a, left, right);
  quickSort(a, left, q - 1);
  quickSort(a, q + 1, right);
}

int main() {
  int n;
  cin >> n;
  int* a = new int[n];
  for (int i = 0; i < n; i++)
    cin >> a[i];
  quickSort(a, 0, n - 1);
  for (int i = 0; i < n; i++)
    cout << a[i] << ' ';
  cout << endl;
  
  delete a;
  return 0;
}

解法二 学生代码 

#include<bits/stdc++.h>
using namespace std;

const int N =1000005;

long long int n;

int arr[N];

void quick_sort(int num[],long long int left,long long int right){
    //判断是否被切分长度为0或1的数组,即有没有排序到最后
	if(left>=right){
		return ;
	}
	long long int i=left-1,j=right+1;
	long long int x =num[(left+right)/2];
	//实现排序过程
	while(i<j){
		do i++;
		while(num[i]<x);
		do j--;
		while(num[j]>x);
		if(i<j)
			swap(num[i],num[j]);
	}
	quick_sort(num,left,j);//对左边进行快速排序
	quick_sort(num,j+1,right);//对右边进行快速排序
}

int main(){
	cin>>n;
	for(long long int i =0;i<n;i++){
		cin>>arr[i];
	}
	quick_sort(arr,0,n-1);
	
	for(long long int i =0;i<n;i++){
		cout<<arr[i]<<" ";
	}
}

7-10 求逆序对数目

考察归并排序

 注意:本问题算法的时间复杂度要求为O(nlogn), 否则得分无效

题目来源:http://poj.org/problem?id=1804
Background
Raymond Babbitt drives his brother Charlie mad. Recently Raymond counted 246 toothpicks spilled all over the floor in an instant just by glancing at them. And he can even count Poker cards. Charlie would love to be able to do cool things like that, too. He wants to beat his brother in a similar task.

Problem
Here's what Charlie thinks of. Imagine you get a sequence of N numbers. The goal is to move the numbers around so that at the end the sequence is ordered. The only operation allowed is to swap two adjacent numbers. Let us try an example:
Start with: 2 8 0 3
swap (2 8) 8 2 0 3
swap (2 0) 8 0 2 3
swap (2 3) 8 0 3 2
swap (8 0) 0 8 3 2
swap (8 3) 0 3 8 2
swap (8 2) 0 3 2 8
swap (3 2) 0 2 3 8
swap (3 8) 0 2 8 3
swap (8 3) 0 2 3 8

So the sequence (2 8 0 3) can be sorted with nine swaps of adjacent numbers. However, it is even possible to sort it with three such swaps:
Start with: 2 8 0 3
swap (8 0) 2 0 8 3
swap (2 0) 0 2 8 3
swap (8 3) 0 2 3 8

The question is: What is the minimum number of swaps of adjacent numbers to sort a given sequence?Since Charlie does not have Raymond's mental capabilities, he decides to cheat. Here is where you come into play. He asks you to write a computer program for him that answers the question in O(nlogn). Rest assured he will pay a very good prize for it.

输入格式:

The first line contains the length N (1 <= N <= 1000) of the sequence;
The second line contains the N elements of the sequence (each element is an integer in [-1000000, 1000000]). All numbers in this line are separated by single blanks.

输出格式:

Print a single line containing the minimal number of swaps of adjacent numbers that are necessary to sort the given sequence.

输入样例:

在这里给出一组输入。例如:

6
-42 23 6 28 -100 65537
输出样例:

在这里给出相应的输出。例如:

5

解法一 教学代码 

#include <iostream>
using namespace std;
 int num;//逆序对个数
int a[10005],b[10005];
void merge(int a[], int b[], int l, int m, int r) {
  int i = l;
  int j = m + 1;
  int k = 0;
  while (i <= m && j <= r) {
    if (a[i] <=a[j])
      b[k++] = a[i++];
    else{
      b[k++] = a[j++];
      num+=m+1-i;//num表示在a[j]左边的数字中(除了已经进入b数组的数字),有多少个数字可以和a[j]组成逆序对(即比a[j]大)
    }
  }
 
  while (i <= m)
    b[k++] = a[i++];
 
  while (j <= r)
    b[k++] = a[j++];
 
  for (int i = 0; i < k; i++)
    a[l + i] = b[i];
}
 
void mergeSort(int a[], int l, int r) {
  if (l == r)
     return;
  int m = (l + r) / 2;
  mergeSort(a, l, m);
  mergeSort(a, m + 1, r);
  int* b = new int[r - l + 1];
  merge(a, b, l, m, r);
  delete b;
}
 
int main() {
  int n;
  cin >> n;
  int* a = new int[n];
  for (int i = 0; i < n; i++)
    cin >> a[i];
  mergeSort(a, 0, n - 1);
  // for (int i = 0; i < n; i++)
  //   cout <<  a[i] << " ";
  cout << num;
  delete a;
  return 0;
 
}

解法二 学生代码

#include<iostream>
using namespace std;
int num;//逆序对个数
int a[10005],b[10005];//排序好的数组存放在b数组

void merge(int a[],int b[],int l,int m,int r){
    int i=l,j=m+1,k=l;//k:数组b的下标
    //将2803分成两个数组(且两数组已经分别排好序),前面一半28,后面一半03,从两个数组的第一个数字开始比较,所以i=l,j=m+1
    while((i<=m)&&(j<=r)){
        if(a[i] <= a[j]){
            b[k++]=a[i++];//左右比较,将较小的数字放入数组b
        }else{
            b[k++]=a[j++];
            num+=m+1-i;//num表示在a[j]左边的数字中(除了已经进入b数组的数字),有多少个数字可以和a[j]组成逆序对(即比a[j]大)
        }
    }
    if(i>m){//将右边剩下的都填入b
        for(int q=j;q<=r;q++){
            b[k++]=a[q];
        }
    }else{//将左边剩下的都填入b
        for(int q=i;q<=m;q++){
            b[k++]=a[q];
        }
    }
    for(int q=l;q<=r;q++){//复制回a数组
        a[q]=b[q];    }
}

void mergesort(int a[],int l,int r){
    if(l>=r)
        return;
    int m=(r+l)/2;
    mergesort(a,l,m);
    mergesort(a,m+1,r);
    merge(a,b,l,m,r);
    
}

int main(){
    int n;
    cin>>n;
    for(int i=0;i<n;i++){
        cin>>a[i];
    }
    mergesort(a,0,n-1);
    cout<<num;
}

第三章 动态规划

7-12 矩阵链相乘问题

 矩阵的乘法定义如下:设A是m×p的矩阵,B是p×n的矩阵,则A与B的乘积为m×n的矩阵,记作C=AB,其中,矩阵C中的第i行第j列元素cij​可以表示为:cij​=Σk=1p​aik​×bkj​=ai1​b1j​+ai2​b2j​+⋯+aip​bpj​.

当多个矩阵相乘时,采用不同的计算顺序所需的乘法次数不相同。例如,A是50×10的矩阵,B是10×20的矩阵,C是20×5的矩阵,
计算ABC有两种方式:(AB)C和A(BC),前一种需要15000次乘法计算,后一种则只需3500次。

设A1​,A2​,...,An​为矩阵序列,Ai​是阶为Pi−1​∗Pi​的矩阵(1≤i≤n)。试确定矩阵的乘法顺序,使得计算A1​A2​...An​过程中元素相乘的总次数最少。

输入格式:

每个输入文件为一个测试用例,每个测试用例的第一行给出一个正整数n(1≤n≤100),表示一共有n个矩阵A1​,A2​,...,An​,第二行给出n+1个整数P0​,P1​...Pn​,以空格分隔,其中1≤Pi​≤100(0≤i≤n),第i个矩阵Ai​是阶为Pi−1​∗Pi​的矩阵。

输出格式:

获得上述矩阵的乘积,所需的最少乘法次数。

输入样例:

在这里给出一组输入。例如:

5
30 35 15 5 10 20
输出样例:

在这里给出相应的输出。例如:

11875

代码如下

/*
矩阵连乘问题,递归方程采用课本P47的递归方程。
与课本P47的程序相比,两者都是填m[][]的上三角,
但本程序是横向填表,而课本程序是斜向填表。 
本程序按照 自底向上,自左向右 的顺序来计算m[][]的上三角。
程序中有记录相应断点到s[][]。 
=============  递归方程 =================================
m[i][j] = min{ m[i][k] + m[k+1][j] + pi-1pkpj }  i < j
m[i][j] = 0  i=j
=======================================================
*/
 
#include <iostream>
using namespace std;
 
static const int MAXN = 2000;
int m[MAXN][MAXN]; //m[i][j]为Ai连乘到Aj的最少乘次数 
int s[MAXN][MAXN]; //s[i][j]为 Ai连乘到Aj的最优解的第一层断点 
int p[MAXN]; //p[i-1]为Ai的行数;p[i]为Ai的列数
int n; //连乘矩阵个数 
 
int dp()
{
    //以下双重循环对m[][]的上三角进行填表
    //填表顺序为自底向上,自左向右 
    for (int i = n; i >= 1; i--) { //i表示行
        for (int j = i; j <= n; j++) { //j表示列,因为只填上三角,所以j的初值为i
            //以下按照矩阵连乘问题的递归公式来求每一个m[i][j]
            if (i == j) {
                m[i][j] = 0;
            } else {
                //若i与j不相等,m[i][j]的初值为断点为i时的最优值
                m[i][j] = m[i+1][j] + p[i-1] * p[i] * p[j];
                s[i][j] = i; //记录当前断点为i
 
                for (int k = i + 1; k < j; k++) { //k用于尝试不同的断点 
                    int t = m[i][k] + m[k+1][j] + p[i-1] * p[k] * p[j];
                    if (t < m[i][j]) { //当前k值作为Ai连乘到Aj的断点能获得更少计算量
                        m[i][j] = t; //刷新最优值
                        s[i][j] = k; //刷新相应断点 
                    }    
                }
            }
        }
    }
 
    return m[1][n];
}
 
void print(int i, int j) {
    if (i==j) { //如果只有一个矩阵,则直接输出该矩阵名称
        cout << "A" << i;
    } else { //否则找出Ai连乘到Aj获得最小乘次数的断点,输出左右两端的连乘序列
        int k = s[i][j]; //找出最优断点
        cout << "("; //如果多个矩阵连乘,左端加括号
        print(i, k); //输出左端连乘序列
        print(k+1, j); //输出右端连乘序列
        cout << ")"; //如果多个矩阵连乘,右端加括号
    }
}
 
int main() {
    cin >> n;
    for (int i = 0; i <= n; i++) 
        cin >> p[i];
    cout << dp() << endl;
    // print(1,n);
    return 0;
}

 7-16 最长公共子序列长度

求两个字符串的最长公共子序列长度。

输入格式:

输入长度≤100的两个字符串。

输出格式:

输出两个字符串的最长公共子序列长度。

输入样例1:
ABCBDAB
BDCABA
输出样例1:
4
输入样例2:
ABACDEF
PGHIK
输出样例2:
0
#include<bits/stdc++.h>
using namespace std;
const int N =1005;
int dp[N][N];
string A,B;

int main(){
	cin>>A>>B;
	memset(dp,0,sizeof(dp));
	int lenA =A.length(),lenB =B.length();
	for(int i =0;i<lenA;i++){
		for(int j =0;j<lenB;j++){
			if(A[i]==B[j])
				dp[i+1][j+1]=dp[i][j]+1;
			else
				dp[i+1][j+1]=max(dp[i+1][j],dp[i][j+1]);
		}
	}
	cout<<dp[lenA][lenB];
}

7-20 0-1背包(动态规划)

给定n(n<=100)种物品和一个背包。物品i的重量是wi(wi<=100),价值为vi(vi<=100),背包的容量为C(C<=1000)。
应如何选择装入背包中的物品,使得装入背包中物品的总价值最大? 在选择装入背包的物品时,对每种物品i只有两个选择:装入或不装入。不能将物品i装入多次,也不能只装入部分物品i。

输入格式:

共有n+1行输入:
第一行为n值和c值,表示n件物品和背包容量c;
接下来的n行,每行有两个数据,分别表示第i(1≤i≤n)件物品的重量和价值。

输出格式:

输出装入背包中物品的最大总价值。

输入样例:

在这里给出一组输入。例如:

5 10
2 6
2 3
6 5
5 4
4 6
输出样例:

在这里给出相应的输出。例如:

15

代码如下:

#include<iostream>
using namespace std;
 
const int maxN = 1000; 
const int MaxC = 1000;
 
int w[maxN];
int v[maxN];
int c;
int n;
int m[maxN][MaxC];
 
/*
根据递归方程式填表
m[i][j] = max(m[i+1][j], m[i+1][j-w[i]]+v[i]) 如果j>=w[i]
          m[i+1][j]                           如果j<w[i]
		  
边界条件:
m[n][j] = 0  如果 j < w[n]
          v[n] 如果j>=w[n] 
*/ 
int dp() {
	//根据边界条件初始化最后一行 
	for (int j = 0; j <= c; j++) {
		if (j >= w[n])
			m[n][j] = v[n];
		else
			m[n][j] = 0;
	}
	
	for (int i = n-1; i >= 1; i--) {
		for (int j = 0; j <= c; j++) {
			if (j >= w[i]) {
				m[i][j] = max(m[i+1][j], m[i+1][j-w[i]]+v[i]);
			} else 
				m[i][j] = m[i+1][j]; 
		}
	}
	return m[1][c];
}
 
//输出问题最优解 
void print() {
	int remain = c;
	//从物品1到物品n,依次比较物品装和不装两种情况,决定是否装入 
	for (int i = 1; i <= n; i++) {
		if (w[i] <= remain) {
			if (m[i+1][remain-w[i]] + v[i] > m[i+1][remain]) {
				cout << i << ' ';
				remain -= w[i];
			}
		}
	}
	cout << endl;
}
 
/*
测试数据: 
5 10
2 6
2 3
6 5
5 4
4 6
*/
 
int main() {
	cin >> n >> c;
	for (int i = 1; i <= n; i++) {
		cin >> w[i] >> v[i];
	}
	cout << dp() << endl; 
	// print();
}

7-21 子集和

给定n个不同的整数的集合,求有多少个子集的和为m

输入格式:

第一行两个数字n(0<n<=100)和m(0<m<=5000),以空格分隔
第二行n个不同的整数,以空格分隔

输出格式:

和为m的子集的个数

输入样例:
5 6
1 2 3 4 5
输出样例:
3

代码如下


#include <iostream>
using namespace std;
int n, m;
int nums[110];
int dp[110][5010];
int solve() {
for (int i = 1; i <= m; i++)
dp[n][i] = 0;
dp[n][0] = 1;
if (m >= nums[n])
dp[n][nums[n]] = 1;
for (int i = n-1; i >= 1; i--) {
for (int j = 0; j <= m; j++) {
dp[i][j] = dp[i+1][j];
if (j >= nums[i])
dp[i][j] += dp[i+1][j - nums[i]];
}
}
return dp[1][m];
}
int main() {
cin >> n >> m;

for (int i = 1; i <= n; i++)
cin >>  nums[i];
cout << solve() << endl;
return 0;
}

第四章 贪心算法

7-26 会场安排问题

题目来源:王晓东《算法设计与分析》

假设要在足够多的会场里安排一批活动,并希望使用尽可能少的会场。设计一个有效的
贪心算法进行安排。(这个问题实际上是著名的图着色问题。若将每一个活动作为图的一个
顶点,不相容活动间用边相连。使相邻顶点着有不同颜色的最小着色数,相应于要找的最小
会场数。)

输入格式:

第一行有 1 个正整数k,表示有 k个待安排的活动。
接下来的 k行中,每行有 2个正整数,分别表示 k个待安排的活动开始时间和结束时间。时间
以 0 点开始的分钟计。

输出格式:

输出最少会场数。

输入样例:
5
1 23
12 28
25 35
27 80
36 50 
输出样例:

在这里给出相应的输出。例如:

3

代码如下

#include<bits/stdc++.h>
using namespace std;
int main()
{
    int n;
    cin >> n;
    int begins[10005], ends[10005];
    for(int i = 0; i < n; i++) {
        cin >> begins[i] >> ends[i];
    }
    sort(begins, begins+n);
    sort(ends, ends+n);
    int count = 0;
	int currentEnd = 0;
    for(int i = 0; i < n; i++) {
        if(begins[i] < ends[currentEnd]) 
        	count++;
		else 
			currentEnd++;
    }
    cout << count << endl;
    return 0;
}

7-28 排队接水 

有n个人在一个水龙头前排队接水,假如每个人接水的时间为Ti,请编程找出这n个人排队的一种顺序,使得n个人的平均等待时间最小。

输入格式:

共两行,第一行为n(1≤n≤1000);第二行分别表示第1个人到第n个人每人的接水时间T1,T2,…,Tn,每个数据之间有1个空格。

输出格式:

输出为这种排列方案下的平均等待时间(输出结果精确到小数点后两位)。

输入样例:
10
56 12 1 99 1000 234 33 55 99 812

输出样例:

291.90

代码如下

/*
贪心策略:
按照用时从小到大排序,用时最短的最先接水
*/
#include <iostream>
#include <algorithm>
#include<iomanip>
using namespace std;
const int SIZE = 1005;
int main()
{
	int n;
	cin >> n;
	int t[n];
	for (int i = 1; i <= n; i++) {
		cin >> t[i];
	}
	sort(t+1, t+n+1);
	
	
	//依次累加每个人等待的时间 
	int total = 0;
	int waitTime = t[1]; 
	for (int i = 2; i <= n; i++) {
		total += waitTime;
		waitTime += t[i];
	}

	/*
	//另外一种做法,直接累加每段等待时间重复的次数 
	int total = 0;
	for (int i = 1; i < n; i++) {
		total += t[i] * (n-i);	
	}
	*/
	
	cout<<setiosflags(ios::fixed)<<setprecision(2);
	cout << float(total) / n << endl;
}

7-35 背包问题

给定n(n<=100)种物品和一个背包。物品i的重量是wi,价值为vi,背包的容量为C(C<=1000)。问:应如何选择装入背包中的物品,使得装入背包中物品的总价值最大? 装入背包的物品可以只装入部分物品。

输入格式:

共有n+1行输入: 第一行为n值和c值,表示n件物品和背包容量c; 接下来的n行,每行有两个数据,分别表示第i(1≤i≤n)件物品的重量和价值。

输出格式:

输出装入背包中物品的最大总价值。

输入样例:
5 10
2 6
2 3
6 5
5 4
4 6
输出样例:
16.67

代码如下

#include<stdio.h>
#include<iostream>
#include<algorithm> 
using namespace std;
const int M = 1000005;

//背包结构体

struct three {
	double w;
	double v;
	double p;
}S[M];

bool cmp(three a, three b) {//添加比较规则 
	return a.p > b.p;
}

int main()
{
	int n;//n个物品
	double m;//承载能力 
	
	cin >> n >> m;
	
	for (int i = 0; i < n; i++) {
		cin >> S[i].w >> S[i].v;
		S[i].p = S[i].v / S[i].w;//性价比 
	}
	sort(S, S + n, cmp);
	double sum = 0.0;//表示贪心记录运走宝物的价值

	for (int i = 0; i < n; i++) {
		if (m > S[i].w) {
			m -= S[i].w;
			sum += S[i].v;
		}
		else {
			sum += S[i].p * m;
			break;//容器已经装满,跳出循环 
		}
	}
	printf("%.2f",sum);
}

7-36 最短路径-Dijkstra

城市的道路四通八达,我们经常需要查找从某地出发到其他地方的路径,当然我们希望能最快到达。现得到去每个地方需要花费的时间,现请你编写程序,计算从特定地点出发到所有城市之间的最短时间。

输入格式:

输入的第一行给出城市数目N (1≤N≤10)和道路数目M和1(表示有向图)或0(表示无向图);

接下来的M行对应每个城市间来往所需时间,每行给出3个正整数,分别是两个城市的编号(从1编号到N)和来往两城市间所需时间。最后一行给出一个编号,表示从此编号地点出发。

输出格式:

输出从特定地点出发到达所有城市(按编号1-编号N顺序输出)的距离(用编号1->编号**: 表示 ),如果无路,请输出no path。每个城市占一行。

输入样例:
5 5 1
1 2 2
1 4 8
2 3 16
4 3 6
5 3 3
1
输出样例:
1->1:0
1->2:2
1->3:14
1->4:8
1->5:no path

代码如下:

#include<bits/stdc++.h>
using namespace std;

const int MAX_N = 15;
const int INF = 0x3f3f3f3f;
int main()
{
    int m,n,directed;
    int g[MAX_N][MAX_N];
    int s[MAX_N];
    int dist[MAX_N];
    cin>>n>>m>>directed;
    memset(g,0x3f,sizeof g);
    for(int i = 0;i<m;i++)
    {
        int u,v,w;
        cin>>u>>v>>w;
        g[u][v]  = w;
        if(!directed)
            g[v][u] = w;
    }
    memset(s,0,sizeof s);
    int source;
    cin>>source;
    s[source] =1;
    for(int i =1;i<=n;i++)
        dist[i] = g[source][i];
    dist[source] = 0;
    
    for(int i =1;i<=n;i++){
        int min_v =INF;
        int min_i;
        for(int j =1;j<=n;j++){
            if(s[j] == 0&& dist[j]<min_v){
                min_v =dist[j];
                min_i =j;
                }
        }
        s[min_i] = 1;
        for(int i =1;i<=n;i++){
            dist[i] = min(dist[i],dist[min_i]+g[min_i][i]);
        }
	}
    for(int i =1;i<=n;i++){
        cout<<source<<"->"<<i<<":";
        if(dist[i]<INF)
            cout<<dist[i]<<endl;
        else
            cout<<"no path"<<endl;
    }
    return 0;
}

第五章 回溯法

7-20 0-1背包(回溯法)

给定n(n<=100)种物品和一个背包。物品i的重量是wi(wi<=100),价值为vi(vi<=100),背包的容量为C(C<=1000)。
应如何选择装入背包中的物品,使得装入背包中物品的总价值最大? 在选择装入背包的物品时,对每种物品i只有两个选择:装入或不装入。不能将物品i装入多次,也不能只装入部分物品i。

输入格式:

共有n+1行输入:
第一行为n值和c值,表示n件物品和背包容量c;
接下来的n行,每行有两个数据,分别表示第i(1≤i≤n)件物品的重量和价值。

输出格式:

输出装入背包中物品的最大总价值。

输入样例:

在这里给出一组输入。例如:

5 10
2 6
2 3
6 5
5 4
4 6
输出样例:

在这里给出相应的输出。例如:

15

代码如下

#include <iostream>
#include<algorithm>
using namespace std;
const int max_N = 110;
int n, c;
struct OBJ{
    double w;
    double v;
}objs[max_N];
int x[max_N];
int cr, cv;
int bestv;
double bound(int t){
    double sum = cv;
    int left = cr;
    for(int i = t; i <= n; i++){
        if(left < objs[i].w){
            sum += left * objs[i].v / objs[i].w;
            break;
        }
        sum += objs[i].v;
        left -= objs[i].w;
    }
    return sum;
}
void backtrack(int t){
    if(t > n){
        if(cv > bestv){
            bestv = cv;
        }
        return;
    }
    if(objs[t].w <= cr){
        x[t] = 1;
        x[t] = 1;
        cr -= objs[t].w;
        cv +=objs[t].v;
        backtrack(t+1);
        cr += objs[t].w;
        cv -= objs[t].v;

    }
    if(bound(t+1) > bestv){
        x[t] = 0;
        backtrack(t+1);
    }

}
bool cmp(OBJ a, OBJ b){
    return a.v / a.w > b.v / b.w;
}
int main(){
    cin >> n >> c;
    for(int i = 1; i <= n; i++)
        cin >> objs[i].w >> objs[i].v;
    sort(objs + 1, objs + 1 + n, cmp);
    cr = c;
    cv = 0;
    backtrack(1);
    cout << bestv << endl;
    return 0;

}

7-31 子集和问题 

设集合S={x1,x2,…,xn}是一个正整数集合,c是一个正整数,子集和问题判定是否存在S的一个子集S1,使S1中的元素之和为c。试设计一个解子集和问题的回溯法,并输出利用回溯法在搜索树(按输入顺序建立)中找到的第一个解。

输入格式:

输入数据第1行有2个正整数n和c,n表示S的大小,c是子集和的目标值。接下来的1行中,有n个正整数,表示集合S中的元素。
是子集和的目标值。接下来的1 行中,有n个正整数,表示集合S中的元素。

输出格式:

输出利用回溯法找到的第一个解,以空格分隔,最后一个输出的后面有空格。当问题无解时,输出“No Solution!”。

输入样例:

在这里给出一组输入。例如:

5 10
2 2 6 5 4
输出样例:

在这里给出相应的输出。例如:

2 2 6 
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
 
ll n,c;
ll a[1000005];
bool flag[1000005];//定义bool类型的数组,标记数字的使用与否; 
ll cnt=0,sum=0;
 
void dfs(ll t,ll w)
{
	if(cnt==1)//如果已经找到一组,返回空,结束深搜;
		return;
	// 深度搜索的终止条件;
	if(t==n+1)
	{
		if(w==c)//搜索到符合条件的值,进行输出这一组解; 
		{
			for(ll i=1;i<=n;i++)
			{
				if(flag[i]==1)
				{
					printf("%lld ",a[i]);
				}
			}
			cnt++;//找到一组cnt+1作为标记; 
		}
		return;
	}
	if(w>c)//如果已经使用的数字和大于所需,在往下搜索就不可能满足要求; 
		return;//因为往下已经不可能满足要求了,所以进行截枝,节省时间; 
	//左枝,即使用这个数; 
	flag[t]=1;//将这个数标记为 1,即使用该数; 
	dfs(t+1,w+a[t]);//递归深搜 
	//右枝,即不使用这个数; 
	flag[t]=0;//将这一数字标记为0,即没有使用这一数字; 
	dfs(t+1,w);//递归深搜 
}
 
int main()
{
	cin>>n>>c; 
	for(ll i=1;i<=n;i++)
	{
		scanf("%lld",&a[i]);
		sum+=a[i];//求数字的和; 
	}
	if(sum<c)//判断所有数字的和是不是大于所需求的数字的和,避免不必要的深搜;
		printf("No Solution!");
	else
	{
		dfs(1,0);//深搜开始 
		if(cnt==0)//如果深搜没有找到合适的解,输出没有解; 
			printf("No Solution!");
	}
	return 0;
}

学生代码

#include<bits/stdc++.h>
using namespace std;
long int n,c;
int a[100005];
bool flag[10005];

long int cnt=0;
long int sum=0;

void dfs(long t ,long int w){
	if(cnt==1)
		return;
	if(t==n+1)
	{
		if(w==c)
		{
			for(long i =1;i<=n;i++){
				if(flag[i]==1){
					cout<<a[i]<<" ";
				}
			}
			cnt++;
		}
		return;
	}
	if(w>c)
	return;
	flag[t]=1;
	dfs(t+1,w+a[t]);
	flag[t]=0;
	dfs(t+1,w);
}
int main(){
	cin>>n>>c;
	for(long int i=1;i<=n;i++){
		cin>>a[i];
		sum+=a[i];
	}
	if(sum<c)
		cout<<"No Solution!";
	else{
			dfs(1,0);
			if(cnt==0)	
				cout<<"No Solution!";
		}
}

7-42 n后问题

在n×n格的棋盘上放置彼此不受攻击的n个皇后。按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。n后问题等价于在n×n格的棋盘上放置n个皇后,任何2个皇后不放在同一行或同一列或同一斜线上

输入格式:

一个数字n

输出格式:

按照深度优先输出所有可行的解

输入样例:
4
输出样例:
2 4 1 3 
3 1 4 2 

代码如下

#include <iostream> 
#include <cmath>
using namespace std;
int n;
int x[31]; 
bool Place(int t) { 
	int i; 
	for (i=1; i<t; i++) 
		if ((abs(t-i) == abs(x[i]-x[t])) || (x[i] == x[t])) 
			return false; 
	return true; 
}  
void Backtrack(int t) { 
	int i;
	if (t>n) 
	{
		
		for (i=1; i<=n; i++) 
			printf("%d ", x[i]);
		printf("\n");
	}
	else
		for (i=1; i<=n; i++) 
		{
			x[t] = i;
			if (Place(t)) 
                Backtrack(t+1);
		}
} 
int main() { 
	while (cin>>n){
		Backtrack(1);
	}
	return 0; 
} 

7-38 旅行售货员

时间复杂度为O(n!)

某售货员要到若干城市去推销商品,已知各城市之间的路程(或旅费)。他要选定一条从驻地出发,经过每个城市一遍,最后回到驻地的路线,使总的路程(或总旅费)最小。

输入格式:

第一行为城市数n

下面n行n列给出一个完全有向图,如 i 行 j 列表示第 i 个城市到第 j 个城市的距离。

输出格式:

一个数字,表示最短路程长度。

输入样例:
3
0 2 1
1 0 2
2 1 0
输出样例:
3

代码如下

#include <iostream>
#include <climits>
using namespace std;
int n;
int a[30][30];
int x[30];
int bestc = 10000;
int cc = 0;
void backtrack (int t){
	if (t > n) {
		if (a[x[n]][1] > 0 && cc + a[x[n]][1] < bestc) {
			bestc = cc + a[x[n]][1];
		}
	}
  else
    for (int i=t; i<=n; i++) {
		if (a[x[t-1]][x[i]] > 0 && cc + a[x[t-1]][x[i]] < bestc) {
			swap(x[t], x[i]);
			cc += a[x[t-1]][x[t]];
			backtrack(t+1);
			cc -= a[x[t-1]][x[t]];
			swap(x[t], x[i]);
		}
    }
}
int main() {
	int i, j;
	cin >> n;
	for (i = 1; i <= n; i++) {
		for (j = 1; j <=n; j++) {
			cin >> a[i][j];
		}
	}
	for (i = 1; i <= n; i++) {
		x[i] = i;
	}
	backtrack(2);
	cout << bestc << endl;
	return 0;
}

 三、2022-2023学年上学期《算法设计与分析》期中小测的题目和题解

A 职业规划

本题为二分搜索的变形题目。在二分搜索算法代码的基础上修改。

将代码17行边界条件的返回值改为right。额外增加字符串数组记录职业名称,并输出对应下标的职业。

#include <iostream>
using namespace std;
int biSearch(int x, int a[], int left, int right)
{
if (left > right) { //当数组中没有元素时
return right; //right为小于x的最大数的下标
}
int middle = (left + right) / 2;
if (a[middle] == x) {//如果找到,直接返回位置
return middle;
}
else if (a[middle] > x) //如果中间的数字大于x,在数组左半部分递归查找
return biSearch(x, a, left, middle - 1);
else //否则在数组右半部分递归查找
return biSearch(x, a, middle + 1, right);
}
const int MAX_N = 1000005;
string jobs[MAX_N];
int a[MAX_N];
int main()
{
int n, x;
cin >> n;
for (int i = 0; i < n; i++) {
cin >> a[i] >> jobs[i];
}
int m;
cin >> m;
while(m--) {
int x;
cin >> x;
if (x < a[0]) {
cout << "none" << endl;
continue;
}
int i = biSearch(x, a, 0, n - 1);
cout << jobs[i] << endl;
}
return 0;
}

B 技能提升

本题是逆序对题目的变形,求逆序对改为求顺序对,可在原代码的基础上修改

#include <iostream>
using namespace std;
//求排好序的a数组中l~m与m+1~r数组元素的交叉逆序数
int crossInv(int a[], int b[], int l, int m, int r) {
int i = l;
int j = m + 1;
int k = 0;
int count = 0; //保存交叉逆序数
while (i <= m && j <= r) {
if (a[i] > a[j]) {
//将a数组l~m子数组的数组元素复制到b数组时,m+1~r子数组已经复制了几个元素到b数组,就是该数组元素的逆序数
count += j - (m + 1);
b[k++] = a[i++];
}
else
b[k++] = a[j++];
}
///将a数组l~m子数组剩余数组元素复制到b数组,同时计算逆序数
while (i <= m) {
b[k++] = a[i++];
count += j - (m + 1);
}
while (j <= r)
b[k++] = a[j++];
for (int i = 0; i < k; i++)
a[l + i] = b[i];
return count;
}
int inverse(int a[], int l, int r) {
if (l == r)
return 0;
int m = (l + r) / 2;
int count = 0; //保存逆序数
count += inverse(a, l, m); //左边数组的逆序数
count += inverse(a, m + 1, r); //右边数组的逆序数
int* b = new int[r - l + 1];
count += crossInv(a, b, l, m, r); //左右数组的交叉逆序数
delete b;
return count;
}
int main() {
int n;
cin >> n;
int* a = new int[n];
for (int i = 0; i < n; i++) {
cin >> a[i];
}
int count = inverse(a, 0, n - 1);
cout << count << endl;
delete a;
return 0;
}

C 毕业要求

本题为子集和问题的原题,忽略输入中的课程名称即可。
29行将课程名输入到一个string变量中
/*
状态表示:
c[i][j]从第i到第n个数和为j的子集个数
状态方程:
c[i][j] = c[i+1][j] + c[i+1][j-a[i]] if j >= a[i]
c[i+1][j]
边界条件:
c[n][j] = 1 if j == 0 or j == a[n]
= 0 else
*/
#include <iostream>
using namespace std;
int n, m;
int nums[110];
int dp[110][5010];
int solve() {
for (int i = 1; i <= m; i++)
dp[n][i] = 0;
dp[n][0] = 1;
if (m >= nums[n])
dp[n][nums[n]] = 1;
for (int i = n-1; i >= 1; i--) {
for (int j = 0; j <= m; j++) {
dp[i][j] = dp[i+1][j];
if (j >= nums[i])
dp[i][j] += dp[i+1][j - nums[i]];
}
}
return dp[1][m];
}
int main() {
cin >> n >> m;
string s;
for (int i = 1; i <= n; i++)
cin >> s >> nums[i];
cout << solve() << endl;
return 0;
}

D 学习计划I

本题为最长公共子串的变形,求字符串的最长公共子串改为求字符串数组的最长公共子串。
本题求最优值。在原题代码的基础上将第1415行的字符数组改为字符串数据即可
/*
状态表示:
c[i][j]为X数组的前i个元素与Y数组的前j个元素的最长公共子序列
状态方程:
c[i][j] = c[i-1][j-1] + 1 如果xi等于yj
c[i][j] = max {c[i][j-1], c[i-1][j]} 如果xi不等于yj
边界条件:
c[i][j] = 0 i=0或者j=0
*/
#include <iostream>
#include <cstring>
using namespace std;
const int MAXLEN = 1000;
int c[MAXLEN][MAXLEN]; //记录子问题XiYj的解
string x[MAXLEN]; //字符序列X
string y[MAXLEN]; //字符序列Y
int m; //X序列的长度
int n; //Y序列的长度
int lcsLength() {
//当x序列为空时,最长公共子序列长度为0
for (int j = 0; j <= n; j++)
c[0][j] = 0;
//当y序列为空时,最长公共子序列长度为0
for (int i = 0; i <= m; i++)
c[i][0] = 0;
//根据递归方程式,自上而下,自左而右填充二维数组
for (int i = 1; i <= m; i++) { //自上而下
for (int j = 1; j <= n; j++) { //自左而右
if (x[i-1] == y[j-1]) //如果xi等于yj
c[i][j] = c[i-1][j-1] + 1;
//如果XiYj-1子问题的解大于Xi-1Yj子问题的解
else if (c[i][j-1] > c[i-1][j])
c[i][j] = c[i][j-1];
//如果XiYj-1子问题的解小于Xi-1Yj子问题的解
else
c[i][j] = c[i-1][j];
}
}
return c[m][n];
}
int main()
{
cin >> m >> n;
for (int i = 0; i < m; i++)
cin >> x[i];
for (int i = 0; i < n; i++)
cin >> y[i];
cout << lcsLength() << endl;
return 0;
}

E 学习计划II

本题为求最长公共子串的最优解。采用递归的方法输出最优解。
由于题目要求最优解优先选择开课顺序靠前的,因此当 c[i-1][j]==c[i][j-1] 时输出 printLCS(i-1, j);
/*
状态表示:
c[i][j]为X数组的前i个元素与Y数组的前j个元素的最长公共子序列
状态方程:
c[i][j] = c[i-1][j-1] + 1 如果xi等于yj
c[i][j] = max {c[i][j-1], c[i-1][j]} 如果xi不等于yj
边界条件:
c[i][j] = 0 i=0或者j=0
*/
#include <iostream>
#include <cstring>
using namespace std;
const int MAXLEN = 1000;
int c[MAXLEN][MAXLEN]; //记录子问题XiYj的解
string x[MAXLEN]; //字符序列X
string y[MAXLEN]; //字符序列Y
int m; //X序列的长度
int n; //Y序列的长度
int lcsLength() {
//当x序列为空时,最长公共子序列长度为0
for (int j = 0; j <= n; j++)
c[0][j] = 0;
//当y序列为空时,最长公共子序列长度为0
for (int i = 0; i <= m; i++)
c[i][0] = 0;
//根据递归方程式,自上而下,自左而右填充二维数组
for (int i = 1; i <= m; i++) { //自上而下
for (int j = 1; j <= n; j++) { //自左而右
if (x[i-1] == y[j-1]) //如果xi等于yj
c[i][j] = c[i-1][j-1] + 1;
//如果XiYj-1子问题的解大于Xi-1Yj子问题的解
else if (c[i][j-1] > c[i-1][j])
c[i][j] = c[i][j-1];
//如果XiYj-1子问题的解小于Xi-1Yj子问题的解
else
c[i][j] = c[i-1][j];
}
}
return c[m][n];
}
//打印Xi和Yj的最长公共子序列
void printLCS(int i, int j) {
if (i == 0 || j == 0) //如果其中一个序列为空,则没有公共子序列
return;
if (x[i-1] == y[j-1]) { //如果
printLCS(i-1, j-1);
cout << x[i-1];
} else if (c[i-1][j] >= c[i][j-1])
printLCS(i-1, j);
else
printLCS(i, j-1);
}
int main()
{
cin >> m >> n;
for (int i = 0; i < m; i++)
cin >> x[i];
for (int i = 0; i < n; i++)
cin >> y[i];
cout << lcsLength() << endl;
//printLCS(m, n);
return 0;
}

F 共同前行

利用二分法猜测本问题的最优值,思路类似作业题“cable master”
#include <iostream>
#include <cstring>
using namespace std;
const int MAX_N = 510;
int t[MAX_N]; //解题时间
int m, n;
void output(int bestt, int i, int j) {
if (j == 1) {
cout << 1 << ' ' << i << endl;
return;
}
int sum = 0;
int k = i;
while (sum + t[k] <= bestt) {
sum += t[k--];
}
output(bestt, k, j-1);
cout << k+1 << " " << i << endl;
}
bool check(int limit) {
int total = 1;
int sum = t[1];
for (int i = 2; i <= m; i++) {
sum += t[i];
if (sum > limit) {
sum = t[i];
total++;
if (total > n)
return false;
}
}
return true;
}
int main() {
cin >> m >> n;
int left = 0, right = 0;
for (int i = 1; i <= m; i++) {
cin >> t[i];
left = max(left, t[i]);
right += t[i];
}
while (left < right) {
int mid = (left + right) / 2;
if (check(mid))
right = mid;
else
left = mid+1;
}
int bestt = left;
output(bestt, m, n);
return 0;
}


  • 18
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值