算法pta

1.算法第一次实验课练习题

7-1 多数组排序

3个整数数组进行整体排序,根据输入的三个数组的元素,输出排序后的结果(从大到小)

输入格式:

第1个数组的长度

第1个数组的各个元素

第2个数组的长度

第2个数组的各个元素

第3个数组的长度

第3个数组的各个元素

输出格式:

所有数组的整体排序

输入样例:

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

3 
79 80 61
3
88 66 77
2
23 90

输出样例:

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

90 88 80 79 77 66 61 23

代码

import java.lang.*;
import java.sql.Array;
import java.util.*;

public class Main{
    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        Integer[] arrays = new Integer[10000];
        for (int i = 0; i < arrays.length; i++) {
            arrays[i] = 0;
        }
        int num = 0;
        for(int i = 0; i < 3; ++i) {
            int n = scan.nextInt();
            for (int j = 0; j < n; ++j) {
                arrays[num++] = scan.nextInt();
            }
        }
        Comparator cmp = new WyComparator();
        Arrays.sort(arrays, cmp);
        for (int i = 0; i < num; ++i) {
            System.out.print(arrays[i]);
            if (i != num - 1)
                System.out.print(" ");
        }
        scan.close();
    }
}
//不是extends Comparator
class WyComparator implements Comparator<Integer>{
    @Override
    public int compare(Integer o1, Integer o2) {
        return o2.compareTo(o1);
    }
}


7-2 快速排序

给定包含n个元素的整型数组a[1],a[2],…,a[n],利用快速排序算法对其进行递增排序,请输出排序过程,即每次Partition之后的数组。每次选择所处理的子数组的第一个元素作为基准元素。

输入格式:

输入为两行,第一行为一个整数n(1<n≤1000),表示数组长度。第二行为n个空格间隔的整数,表示待排序的数组。

输出格式:

输出为若干行,每行依次输出Partition后的数组,每个元素后一个空格。

输入样例:

5
4 5 3 2 1

输出样例:

2 1 3 4 5 
1 2 3 4 5 
1 2 3 4 5 

代码

#include<iostream>
using namespace std;
int n;
void print(int *arr){
	for(int i=0;i<n;i++){
		printf("%d ",arr[i]);
	}
	cout<<endl;
}
void swap(int &a,int &b){
	int temp=a;
	a=b;
	b=temp;
}
void quicksort(int *arr,int left,int right){
	if(left>=right){
		return;
	}
	int l=left;
	int r=right;
	int key=arr[left];
	while(l<r){
		while(l<r&&arr[r]>key){
			r--;
		}
		while(l<r&&arr[l]<=key){
			l++;
		}
		swap(arr[r],arr[l]);
	}
	//将起始值和ij相等的交换,此时左边数都小,右边数都大 
	swap(arr[l],arr[left]);
	print(arr);
	quicksort(arr,left,l-1);
	quicksort(arr,l+1,right);
}
int main(){
	cin>>n;
	int arr[n]; 
	for(int i=0;i<n;i++){
		scanf("%d",&arr[i]);
	}
	quicksort(arr,0,n-1);
	for(int i=0;i<n;i++){
		printf("%d ",arr[i]);
	}
	return 0;
}

7-3 归并排序

知识点:归并排序

给定你一个长度为 n 的整数数列。

请你使用归并排序对这个数列按照从小到大进行排序。

并将排好序的数列按顺序输出。

输入格式:

输入共两行,第一行包含整数 n(1≤n≤100000)。

第二行包含 n 个整数(所有整数均在 1∼10^9 范围内),表示整个数列。

输出格式:

输出共一行,包含 n 个整数,表示排好序的数列。

输入样例:

5
3 1 2 4 5

输出样例:

1 2 3 4 5 

代码

#include <iostream>

using namespace std;

const int N = 100010;
int a[N], temp[N], n;

void merge_sort(int l, int r)
{
    if(l >= r) return;
    int mid = l + r >> 1, i = l, j = mid + 1, k = 0;
    merge_sort(l, mid), merge_sort(mid + 1, r);
    while(i <= mid && j <= r)
    {
        if(a[i] <= a[j]) temp[k++] = a[i++];
        else temp[k++] = a[j++];
    }
    while(i <= mid) temp[k++] = a[i++];
    while(j <= r) temp[k++] = a[j++];
    for(int i = l, j = 0; i <= r; i++, j++)
    {
        a[i] = temp[j];
    }
}

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

2算法实验一

选择题

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DZy4M7PY-1672303453668)(算法pta.assets/image-20221228164000774.png)]

6-1 猴子吃桃-递归(函数题)

小猴子第一天摘下桃子若干,当即吃掉一半,还不过瘾,又多吃一个,第二天又将剩下的桃子吃掉一半多一个,以后每天吃掉前一天剩下的一半多一个,到第n天准备吃的时候只剩下一个桃子。聪明的你,请帮悟空算一下,他第一天开始吃的时候桃子一共有多少个呢?。
请通过递归形式实现。

函数接口定义:

int Peach(int day);

该函数返回第day天所剩的桃子。用递归实现。

裁判测试程序样例:

#include <stdio.h>
int n;
int Peach(int day);
int main ()
{
    scanf("%d", &n);
    printf("%d\n", Peach(1));
    return 0;
}

/* 请在这里填写答案 */

输入样例:

3

输出样例:

10

代码

int Peach(int day)
{  
    static int sum=1;
    if(day == n)
    {
        return sum;
    }
    sum=(sum+1)*2;
    Peach(day+1);
    
}

7-1 多数组排序

3个整数数组进行整体排序,根据输入的三个数组的元素,输出排序后的结果(从大到小)

输入格式:

第1个数组的长度

第1个数组的各个元素

第2个数组的长度

第2个数组的各个元素

第3个数组的长度

第3个数组的各个元素

输出格式:

所有数组的整体排序

输入样例:

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

3 
79 80 61
3
88 66 77
2
23 90

输出样例:

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

90 88 80 79 77 66 61 23

代码

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

struct Node{
	int number;
};
//递减 
bool sort_number(Node a,Node b){
	return a.number < b.number; 
}

int main(){
	
	vector<int>v;
	
	for(int i = 0; i < 3; i++){
		int nums;
		cin >> nums;
		for(int j = 0; j < nums; j++){
			int temp;
			cin >> temp;
			v.push_back(temp);
		}
	}
	
	//冒泡排序
	int flag = true;
	for(int i = 0; i < v.size() - 1 && flag; i++){
		
		flag = false;
		for(int j = v.size()-2; j >= i; j--){ 
		
			if(v[j] < v[j+1]){
				int temp = v[j];
				v[j] = v[j+1];
				v[j+1] = temp;
				flag = true;		
			}
		}
	} 
	
	for(int i = 0; i < v.size(); i++){
		if( i == 0)
			cout << v[i];
		else
			cout << ' ' << v[i];	
	}
	
} 

7-2 A011 递归练习2

根据递推式子C(m,n)=C(m-1,n)+C(m-1,n-1),求组合数C(m,n)。注意递推的终止条件是C(m,1)=m;以及一些m和n取值的一些特殊情况,如m < 0或n < 0或m < n时,C(m,n)值为0,m和n相等时,C(m,n)=1等。

输入格式:

第1行是测试数据的组数n,后面跟着n行输入。每组测试数据占1行,包括一个正整数m和一个正整数n。

输出格式:

输出组合数C(m,n)。

输入样例:

2
1 100
100 1

输出样例:

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

0
100

代码

#include<stdio.h>
int C(int m,int n){
    if(m<0||n<0||m<n){
        return 0;
    }
    else if(m==n){
        return 1;
    }
   
    else return C(m-1,n)+C(m-1,n-1);
}
int main(){
    int t,m,n;
    scanf("%d",&t);
    for(int i=0;i<t;i++){
        scanf("%d %d",&m,&n);
        printf("%d\n",C(m,n));
    }
    
}

7-3 A012 递归练习3

核反应堆中有α和β两种粒子。每秒钟内一个α粒子可以产生3个β粒子,而一个β粒子可以产生1个α粒子和2个β粒子。若在t=0时刻反应堆中有一个α粒子,求t时刻反应堆中分别有多少个α粒子和β粒子。

输入格式:

输入有多个整数t,每个一行。

输出格式:

输出t时刻反应堆里分别有多少个α粒子和β粒子。

输入样例:

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

6

输出样例:

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

183 546

代码

import java.util.Scanner;
public class Main {
    public static void main(String[] args) {
        Scanner sc=new Scanner(System.in);
        while(sc.hasNext()){
            int t=sc.nextInt();
            int a=1,b=0;
            while(t>0){
                int c=a;
                a=b;
                b=3*c+2*b;
                t--;
            }
            System.out.println(a+" "+b);
        }
    }
}

3分治策略

选择题

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nPRSFuoY-1672303453670)(算法pta.assets/image-20221228202246959.png)]

填空题

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Tjs1Ngdc-1672303453670)(算法pta.assets/image-20221228202347371-16722302311891.png)]

7-1 输出全排列

请编写程序输出前n个正整数的全排列(n<10),并通过9个测试用例(即n从1到9)观察n逐步增大时程序的运行时间。

输入格式:

输入给出正整数n(<10)。

输出格式:

输出1到n的全排列。每种排列占一行,数字间无空格。排列的输出顺序为字典序,即序列a1,a2,⋯,a**n排在序列b1,b2,⋯,b**n之前,如果存在k使得a1=b1,⋯,a**k=b**k 并且 a**k+1<b**k+1。

输入样例:

3

输出样例:

123
132
213
231
312
321

代码

#include <stdio.h>
#include <stdlib.h>
int a[11],b[11];///数组a用于判读数字是否选过,数组b用来存储符合条件的数字,即(未被选过,a[x]=0)
int n;
void find(int a[],int b[],int x)
{
    if(x==n+1)//当所有位置都遍历之后,输出
    {
        for(int i=1; i<=n; i++)
            printf("%d",b[i]);
        printf("\n");
    }
    for(int i=1; i<=n; i++)
    {
        if(a[i]==0)//判断该数字是否被选择过,未被选择过的加入数组b中
        {
            a[i]=1;
            b[x]=i;
            find(a,b,x+1);
            a[i]=0;
        }
    }
}
int main()
{
    scanf("%d",&n);
    find(a,b,1);
    return 0;
}

7-2 汉诺塔II

经典的汉诺塔问题经常作为一个递归的经典例题存在。可能有人并不知道汉诺塔问题的典故。汉诺塔来源于印度传说的一个故事,上帝创造世界时作了三根金刚石柱子,在一根柱子上从下往上按大小顺序摞着64片黄金圆盘。上帝命令婆罗门把圆盘从下面开始按大小顺序重新摆放在另一根柱子上。并且规定,在小圆盘上不能放大圆盘,在三根柱子之间一回只能移动一个圆盘。有预言说,这件事完成时宇宙会在一瞬间闪电式毁灭。也有人相信婆罗门至今仍在一刻不停地搬动着圆盘。恩,当然这个传说并不可信,如今汉诺塔更多的是作为一个玩具存在。Gardon就收到了一个汉诺塔玩具作为生日礼物。
  Gardon是个怕麻烦的人(恩,就是爱偷懒的人),很显然将64个圆盘逐一搬动直到所有的盘子都到达第三个柱子上很困难,所以Gardon决定作个小弊,他又找来了一根一模一样的柱子,通过这个柱子来更快的把所有的盘子移到第三个柱子上。下面的问题就是:当Gardon在一次游戏中使用了N个盘子时,他需要多少次移动才能把他们都移到第三个柱子上?很显然,在没有第四个柱子时,问题的解是2^N-1,但现在有了这个柱子的帮助,又该是多少呢?

输入格式:

包含多组数据,每个数据一行,是盘子的数目N(1<=N<=64)。

输出格式:

对于每组数据,输出一个数,到达目标需要的最少的移动数。

输入样例:

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

1
3
12

输出样例:

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

1
5
81

代码

#include<stdio.h>
#include<math.h>
int min = 999999;
int a[65];
int SUM()
{
	a[1] = 1;
	a[2] = 3;
	for(int i=3;i<=64;i++)
	{
		min = 999999;
		for(int k=1;k<i;k++)
		{
			if(2*a[k]+pow(2,i-k)-1 < min)
			{
				min = 2*a[k] + pow(2,i-k)-1;
			}
			a[i] = min;
		 } 
	}
}
int main()
{
	int n ;
	SUM();
	while(scanf("%d",&n) != EOF)
	{
		printf("%d\n",a[n]);
	}
	
}

7-3 棋盘覆盖

在一个2k∗2k(k为正整数,k<=10,length=2k)个方格组成的棋盘中,恰有一个方格与其他方格不同,称该方格为一特殊方格(其坐标为a,b,分别代表行坐标号和列坐标号),以及有四种L型骨牌(如下图)。求用若干块这种L型骨牌实现除该特殊点棋盘的全覆盖。(本题要求采用分治算法做)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EornjmZs-1672303453671)(算法pta.assets/3cc3d212-0095-43e5-8231-0fbda4d3e922.png)]

输入格式:

输入三个数,分别是a,b,length.

输出格式:

输出整个棋盘。其中特殊方格填为0,然后铺棋盘的顺序为:先铺四个子棋盘交界的部分,然后递归的对每个子棋盘按照左上,右上,右下,左下的顺时针顺序铺满棋盘。每一块骨牌中三个方格数字相同,按照顺序标号,即第一块骨牌全标为1,第二块骨牌全标为2,…,以此类推。输出的每个数占4个场宽,右对齐。

输入样例:

1 1 4

表示:特殊格子为(1,1),棋盘有4行4列。

输出样例:

   0   2   3   3
   2   2   1   3
   5   1   1   4
   5   5   4   4

提示:先铺三个1,再铺三个2,…,最后铺三个5(即先处理子问题交界的地方,再处理左上,右上,右下,左下的子问题).

代码

#include<stdio.h>
int tile = 1;
int Board[1000][1000];
void ChessBoard(int tr,int tc,int dr,int dc,int size); 
int main()
{
	int aa,bb,length;
    scanf("%d %d %d",&aa,&bb,&length);
	ChessBoard(1,1,aa,bb,length);
 
	for(int i=1; i<=length; i++)
	{
		for(int j=1; j<=length; j++)
		{
			printf("%4d",Board[i][j]);
		}
		printf("\n");
	}
	return 0;
}
 
void ChessBoard(int tr,int tc,int dr,int dc,int size)
{
	if(size == 1)
	{
		return;
	}
	int t = tile++;
	int s = size/2;
	if(dr<tr+s && dc<tc+s)
	{
		ChessBoard(tr,tc,dr,dc,s);
	}
	else
	{
		Board[tr+s-1][tc+s-1] = t;
		ChessBoard(tr,tc,tr+s-1,tc+s-1,s);
	}
	if(dr<tr+s && dc>=tc+s)
	{
		ChessBoard(tr,tc+s,dr,dc,s);
	}
	else
	{
		Board[tr+s-1][tc+s] = t;
		ChessBoard(tr,tc+s,tr+s-1,tc+s,s);
	}
	if(dr>=tr+s && dc>=tc+s)
	{
		ChessBoard(tr+s,tc+s,dr,dc,s);
	}
	else
	{
		Board[tr+s][tc+s] = t;
		ChessBoard(tr+s,tc+s,tr+s,tc+s,s);
	}
 
	if(dr>=tr+s && dc<tc+s)
	{
		ChessBoard(tr+s,tc,dr,dc,s);
	}
	else
	{
		Board[tr+s][tc+s-1] = t;
		ChessBoard(tr+s,tc,tr+s,tc+s-1,s);
	}
}

7-4 排序

给定N个(长整型范围内的)整数,要求输出从小到大排序后的结果。

本题旨在测试各种不同的排序算法在各种数据情况下的表现。各组测试数据特点如下:

数据1:只有1个元素;

数据2:11个不相同的整数,测试基本正确性;

数据3:103个随机整数;

数据4:104个随机整数;

数据5:105个随机整数;

数据6:105个顺序整数;

数据7:105个逆序整数;

数据8:105个基本有序的整数;

数据9:105个随机正整数,每个数字不超过1000。

输入格式:

输入第一行给出正整数N(≤105),随后一行给出N个(长整型范围内的)整数,其间以空格分隔。

输出格式:

在一行中输出从小到大排序后的结果,数字间以1个空格分隔,行末不得有多余空格。

输入样例:

11
4 981 10 -17 0 -20 29 50 8 43 -5

输出样例:

-20 -17 -5 0 4 8 10 29 43 50 981

代码

#include<stdio.h>
void Merge(int arr[],int left,int mid,int right)
{
	int temp[right-left+1];
	for(int i=left;i<=right;i++)
	{
		temp[i-left]=arr[i];
	}
	int i=left;
	int j=mid+1;
	for(int k=left;k<=right;k++)
	{
		if(i>mid && j<=right)
		{
			arr[k]=temp[j-left];
			j++;
		}
		else if(i<= mid && j>right)
		{
			arr[k] = temp[i-left];
			i++;
		}
		else if(temp[i-left]>temp[j-left])
		{
			arr[k]= temp[j-left];
			j++;
		}
		else if(temp[i-left]<= temp[j-left])
		{
			arr[k] = temp[i-left];
			i++;
		}
	}
	
}
void Mergesort(int arr[],int left,int right)
{
	if(left>=right)
	{
		return ;
	}
	int mid = (right+left)/2;
	Mergesort(arr,left,mid);
	Mergesort(arr,mid+1,right);
	Merge(arr,left,mid,right);
}
int main()
{
	
	int n;
	scanf("%d",&n);
	int a[n+1];
	for(int i=0;i<n;i++)
	{
		scanf("%d",&a[i]);
	}
	Mergesort(a,0,n-1);
	for(int i=0;i<n-1;i++)
	{
		printf("%d ",a[i]);
	}
    printf("%d",a[n-1]);
 } 

7-5 排列的字典序问题

n个元素 {1,2, …,n} 有n!个不同的排列。将这 n! 个排列按字典序排列, 并编号为 0,1,…,n!-1 。每个排列的编号为其字典序值。例如,当n=3时,6个不同排列的字典序值如下:

字典序值012345
排列123132213231312321

输入格式:

第一行是元素个数n(1<n<=8),接下来的1行是n个元素{1,2,…,n}的一个排列。题目不会给出最后一个排列。

输出格式:

第一行输出计算出的排列的字典序值,第二行输出按字典序排列的下一个排列。

输入样例:

3
2 3 1

输出样例:

3
3 1 2

代码

#include<stdio.h>
#include<stdlib.h>
void SWAP(int * a, int * b)
{
    long long t;
    t = *a;
    *a = *b;
    *b = t;
}
int main()
{
    int n,i,k,j,t,order[100];
    int lis,f[100],mid,h;
    f[0]=1;
    for(i=1;i<=22;i++)
        f[i]=f[i-1]*i;
    if(scanf("%d",&n)!=EOF)
    {
        for(i=0;i<n;i++)
            scanf("%d",&order[i]); 
        if(n==1)    
			printf("0\n1\n");
        else if(n>=2)
        {
            lis=0;
            for(i=0,k=n-1;i<n-1;i++,k--)
            {
                t=0;
                for(j=0;j<i;j++)
                    if(order[j]<order[i])    
						t++;
                lis+=(order[i]-1-t)*f[k];
            }
            printf("%d\n",lis);
 
            for(i = n-2; i >= 0; i--)
            {
                if(order[i] < order[i+1])
                {
                    j = i;
                    for(k = n-1; k > j; k--)
                    {
                        if(order[k] > order[j])
                        {
                            mid = j+(n-j)/2;
                            SWAP(&order[j], &order[k]);
                            for(j++, h = 1; j <= mid; j++, h++)
                                SWAP(&order[j], &order[n-h]);
                        }
                    }
                    break;
                }
            }
            for(i=0; i < n-1; i++)
                printf("%d ",order[i]);
            printf("%d\n",order[i]);
        }
    }
    return 0;
}

7-6 众数

给出若干个正整数,请找出出现次数最多的数。

输入格式:

在一行中输入若干个数,以空格间隔(读入数的总个数不超过5000)。

输出格式:

输出出现次数最多的数(若答案不唯一,输出最小的那个)。

输入样例:

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

18 999 999 2100000000

输出样例:

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

999

代码


#include<stdio.h>
int main()
{
	int len=0;
	int a[5000]={0};
	int b[5000]={0};
	while(scanf("%d",&a[len])!=EOF)
	{
		for(int i=0;i<len;i++)
		{
			if(a[i] == a[len])
			{
				b[i]++;
			}
		}
		len++;
	}
	int max=0,mai=0;
	for(int i=0;i<len;i++)
	{
		if(max<b[i])
		{
			max=b[i];
			mai = i;
		}
	}
	printf("%d",a[mai]);	
}

7-7 有重复元素的全排列

计算给定的n个数有多少种排列方式,即求全排列(可能出现重复的元素)

输入格式:

第一行输入数字的数量n(n>2),第二行给出每一个数字。

输出格式:

一个数字,不同排列方式的数量。

输入样例:

3
1 2 2

输出样例:

3

代码

#include <iostream>
using namespace std;
int count=0;
void swap(char &a,char &b)
{
	char temp;
	temp=a;
	a=b;
	b=temp;
}
int finish(char list[],int k,int i)
{
	if(i>k)
	{
		for(int j=k;j<i;j++)
			if(list[j]==list[i])
				return 0;
	}
	return 1;
}
void perm(char list[],int k,int m)
{
	if(k==m)
	{
		count++;
	}
	for(int i=k;i<=m;i++)
	{
		if(finish(list,k,i))
		{
			swap(list[k],list[i]);
			perm(list,k+1,m);
			swap(list[k],list[i]);
		}
	}
}
int main()
{
	int i,n;
	cin>>n;
	char *a=new char[n];
 
	for(i=0;i<n;i++)
		cin>>a[i];
	perm(a,0,n-1);
	cout<<count<<endl;
	return 0;
}

7-8 半数集

给定一个自然数n,由n 开始可以依次产生半数集set(n)中的数如下(注意半数集是多重集)。

  1. n∈set(n);
  2. 在n 的左边加上一个自然数,但该自然数不能超过最近添加的数的一半;
  3. 按此规则进行处理,直到不能再添加自然数为止。

例如,set(6)={6,16,26,126,36,136}。半数集set(6)中有6 个元素。

输入格式:

一个自然数n(0<n<1000)

输出格式:

半数集set(n)中的元素个数

输入样例:

6

输出样例:

6

代码

#include<stdio.h>
int num=1;
int dfs(int n)
{
	
	for(int i=1;i<=n/2;i++)
	{
		num++;
		dfs(i);
	}
	return num;
}
int main()
{
	int n;
	scanf("%d",&n);
	dfs(n);
	printf("%d",num);
 }

7-9 集合划分

当n=4 时,集合{1,2,3,4}可以划分为15个不同的非空子集如下:

{{1},{2},{3},{4}},
{{1,2},{3},{4}},
{{1,3},{2},{4}},
{{1,4},{2},{3}},
{{2,3},{1},{4}},
{{2,4},{1},{3}},
{{3,4},{1},{2}},
{{1,2},{3,4}},
{{1,3},{2,4}},
{{1,4},{2,3}},
{{1,2,3},{4}},
{{1,2,4},{3}},
{{1,3,4},{2}},
{{2,3,4},{1}},
{{1,2,3,4}}

那么n个元素的集合{1,2,.,n }可以划分为多少个非空子集?

输入格式:

一个正整数n

输出格式:

一个数字,表示n个元素的集合可以划分非空子集的个数

输入样例:

5

输出样例:

52

代码

#include<stdio.h>
int fl(int n,int m)
{
	if(n<=2)
	{
		return 1;
	}
	else if(n == m || m==1)
	{
		return 1;
	 } 
	 else
	 {
	 	return fl(n-1,m-1) + m*fl(n-1,m); 
	 }
	
}
int main()
{
	int n;
	scanf("%d",&n);
	int sum=0;
	for(int i=1;i<=n;i++)
	{
		sum += fl(n,i);
	}
	printf("%d",sum);
	return 0;
 } 

7-10 快速排序

给定包含n个元素的整型数组a[1],a[2],…,a[n],利用快速排序算法对其进行递增排序,请输出排序过程,即每次Partition之后的数组。每次选择所处理的子数组的第一个元素作为基准元素。

输入格式:

输入为两行,第一行为一个整数n(1<n≤1000),表示数组长度。第二行为n个空格间隔的整数,表示待排序的数组。

输出格式:

输出为若干行,每行依次输出Partition后的数组,每个元素后一个空格。

输入样例:

5
4 5 3 2 1

输出样例:

2 1 3 4 5 
1 2 3 4 5 
1 2 3 4 5 

代码

#include<stdio.h>
int n;
void swap(int a[],int left,int right)
{
	int w = a[left];
	a[left] = a[right];
	a[right] = w;
}
void quicksort(int a[],int left,int right)
{
	if(left >= right)
	{
		return ;
	}
	int i=left,j = right;
	int key = a[left];
	while(i < j)
	{
		while(i<j && a[j] > key)
		{
			j--;
		}
		while(i<j && a[i] <= key)
		{
			i++;
		}
		swap(a,i,j);
	}
	swap(a,left,i);
	for(int k=0;k<n;k++)
	{
		printf("%d ",a[k]);
	}
    printf("\n");
	quicksort(a,left,i-1);
	quicksort(a,i+1,right);
}
int main()
{

	scanf("%d",&n);
	int a[n+1];
	for(int i=0;i<n;i++)
	{
		scanf("%d",&a[i]);
	}
	quicksort(a,0,n-1);
	for(int i=0;i<n;i++)
	{
		printf("%d ",a[i]);
	}

}

7-11 二路归并排序

给定一个整数序列,请按非递减序输出采用二路归并排序(递归法)的各趟排序后的结果(每完成一次归并操作就输出归并后的结果)。

输入格式:

测试数据有多组,处理到文件尾。每组测试数据第一行输入一个整数n(1≤n≤100),第二行输入n个整数。

输出格式:

对于每组测试,输出若干行,每行是一趟排序后的结果,每行的每两个数据之间留一个空格。

输入样例:

6
73 12 27 98 81 64

输出样例:

12 73 27 98 81 64
12 27 73 98 81 64
12 27 73 81 98 64
12 27 73 64 81 98
12 27 64 73 81 98

来源:

[1] 黄龙军, 等. 数据结构与算法, 上海:上海交通大学出版社, 2022.7. ISBN: 9787313269881
[2] 黄龙军, 等. 数据结构与算法(Python版), 上海: 上海交通大学出版社, 2023. (In Press)

代码

#include<stdio.h>
#include<stdlib.h>
 int a[110];
 int n;
void merge(int a[],int tem[],int p,int q,int r)
{
    int i=p;int j=q+1;int pos=p;
    while(i<=q&&j<=r)
    {
         if(a[i]<a[j])
         tem[pos++]=a[i++];
         else
         tem[pos++]=a[j++];
    }
    while(i<=q)
       tem[pos++]=a[i++];
    while(j<=r)
      tem[pos++]=a[j++];
     while(p<=r)
     {
       a[p]=tem[p];
      p++;
     }
}
void print(int s[],int n)
{
    int t=0;
    for(t=0;t<n;t++)
    {
      printf("%d",s[t]);
       if(t==n-1)
     {
      printf("\n");
     }else{
     printf(" ");
    }
    }
}
void msort(int a[],int tem[],int p,int r)
{
    if(p<r)
    {
      int q=(p+r)/2;
      msort(a,tem,p,q);
      msort(a,tem,q+1,r);
      merge(a,tem,p,q,r);
      print(a,n);
     }
}
void merge_sort(int a[],int n)
{
        int tem[n];
        msort(a,tem,0,n-1);
}
int main()
{
    int i=0;
    while(~scanf("%d",&n))
    {
        for(i=0;i<n;i++)
        scanf("%d",&a[i]);
        merge_sort(a,n);
    }
       return 0;
}

4递归与分支

7-2 汉诺塔II

经典的汉诺塔问题经常作为一个递归的经典例题存在。可能有人并不知道汉诺塔问题的典故。汉诺塔来源于印度传说的一个故事,上帝创造世界时作了三根金刚石柱子,在一根柱子上从下往上按大小顺序摞着64片黄金圆盘。上帝命令婆罗门把圆盘从下面开始按大小顺序重新摆放在另一根柱子上。并且规定,在小圆盘上不能放大圆盘,在三根柱子之间一回只能移动一个圆盘。有预言说,这件事完成时宇宙会在一瞬间闪电式毁灭。也有人相信婆罗门至今仍在一刻不停地搬动着圆盘。恩,当然这个传说并不可信,如今汉诺塔更多的是作为一个玩具存在。Gardon就收到了一个汉诺塔玩具作为生日礼物。
  Gardon是个怕麻烦的人(恩,就是爱偷懒的人),很显然将64个圆盘逐一搬动直到所有的盘子都到达第三个柱子上很困难,所以Gardon决定作个小弊,他又找来了一根一模一样的柱子,通过这个柱子来更快的把所有的盘子移到第三个柱子上。下面的问题就是:当Gardon在一次游戏中使用了N个盘子时,他需要多少次移动才能把他们都移到第三个柱子上?很显然,在没有第四个柱子时,问题的解是2^N-1,但现在有了这个柱子的帮助,又该是多少呢?

输入格式:

包含多组数据,每个数据一行,是盘子的数目N(1<=N<=64)。

输出格式:

对于每组数据,输出一个数,到达目标需要的最少的移动数。

输入样例:

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

1
3
12

输出样例:

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

1
5
81

代码

#include<stdio.h>
#include<math.h>
int main()
{
	int n;
	int i,j;
	int a[100000];
	a[0]=1;
	int k=0;
	int sum=1;
	for(i=2;i<=64;i++)
	{
		for(j=1;j<=i;j++)
		{
			sum=sum+pow(2.0,i-1);
			a[++k]=sum;
		}
	}
	while(scanf("%d",&n)!=EOF)
	{
		printf("%d\n",a[n-1]);
	}
}

5动态规划题目集

选择题

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-C1KXxhV8-1672303453671)(算法pta.assets/image-20221228205833859.png)]

2填空题

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YoldMz8D-1672303453671)(算法pta.assets/image-20221228205933173.png)]

(函数)6-1 求解会议安排问题(动态规划法)**

陈老师是一个比赛队的主教练。有一天,他想与团队成员开会,应该为这次会议安排教室。教室非常缺乏,所以教室管理员必须接受订单和拒绝订单以优化教室的利用率。如果接受一个订单,该订单的开始时间和结束时间成为一个活动。每个时间段只能安排一个订单(即假设只有一个教室)。请你找出一个最大化的总活动时间的方法。你的任务是这样的:读入订单,计算所有活动(接受的订单)占用时间的最大值。

函数接口定义:

void solve();

裁判测试程序样例:

#include <stdio.h>
#include <string.h>
#include <vector>
#include <algorithm>
#include <iostream>
using namespace std;
#define MAX 101
struct NodeType
{
    int b;                            //开始时间
    int e;                            //结束时间
    int length;                        //订单的执行时间
};
 bool cmp(const NodeType &a,const NodeType &b)
{    //用于排序的运算符重载函数
        return a.e<b.e;                //按结束时间递增排序
}
int n;                            //订单个数
NodeType A[MAX];    //存放订单
int dp[MAX];                    //动态规划数组
int pre[MAX];                    //pre[i]存放前驱订单编号

void solve();
int main()
{
    cin>>n;
    for(int i=0;i<n;i++)
      cin>>A[i].b>>A[i].e;
    for (int i=0; i<n; i++)
        A[i].length=A[i].e-A[i].b;
    solve();
    cout<<dp[n-1]; 
    return 0;
}

/* 请在这里填写答案 */

输入格式:

第一行是一个整数n,接着的n行中每一行包括两个整数b和e,其中b是一个订单开始时间,e是的结束时间。。

输出格式:

输出一行包括所有活动占用时间的最大值。

输入样例1:

11
1 4
3 5
0 6
5 7
3 8
5 9
6 10
8 11
8 12
2 13
12 15

输出样例1:

13

代码

void solve()                    //求dp和pre
{
    memset(dp,0,sizeof(dp));    //dp数组初始化
    stable_sort(A,A+n,cmp);        //采用稳定的排序算法
    dp[0]=A[0].length;
    pre[0]=-1;
    for (int i=1;i<n;i++)
    {
        int low=0, high=i-1;
        while(low<=high)        //在A[0..i-1]中查找结束时间早于A[i]开始时间的最晚订单A[low-1]
        {
            int mid=(low+high)/2;
            if(A[mid].e<=A[i].b)
                low=mid+1;
            else
                high=mid-1;
        }
        if (low==0)                //特殊情况
        {
            if(dp[i-1]>=A[i].length)
            {
                dp[i]=
dp[i-1]
;
                pre[i]=-2;        //不选中订单i
            }
            else
            {
                dp[i]=
A[i].length
;
                pre[i]=-1;        //没有前驱订单
            }
        }
        else                    //A[i]前面最晚有兼容订单A[low-1]
        {
            if (dp[i-1]>=dp[low-1]+A[i].length)
            {
                dp[i]=
dp[i-1]
;
                pre[i]=-2;        //不选中订单i
            }
            else
            {
                dp[i]=
dp[low-1]+A[i].length
;
                pre[i]=low-1;    //选中订单i
            }
        }
    }
}

6-2 (函数)求解矩阵最小路径和问题(动态规划法)

给定一个m行n列的矩阵,从左上角开始每次只能向右或者向下移动,最后到达右下角的位置,路径上的所有数字累加起来作为这条路径的路径和。求所有路径和中最小路径和。

函数接口定义:

void minpath();//求最小和路径ans
void Disppath();//输出最小和路径

裁判测试程序样例:

#include <stdio.h>
#include <vector>
#include <stack> 
#include <string.h>
#include <iostream>
using namespace std;
#define MAXN 100
int a[MAXN][MAXN];
int n,m;
int ans=0;
int dp[MAXN][MAXN];
int pre[MAXN][MAXN];

void minpath();//求最小和路径ans
void Disppath();//输出最小和路径

int main()
{    memset(pre,0,sizeof(pre));
    memset(dp,0,sizeof(dp));
    scanf("%d%d",&m,&n);
    for (int i=0;i<m;i++)
        for (int j=0;j<n;j++)
            scanf("%d",&a[i][j]);
    minpath();    
    Disppath();    
    printf("\n%d",ans);    
    return 0;
}

/* 请在这里填写答案 */

输入格式:

首先输入行数m及列数n,接下来输入m行,每行n个数。

输出格式:

输出第一行为最小路径(假定测试数据中的最小路径唯一),第2行为最小路径和。

输入样例1:

4 4
1 3 5 9
8 1 3 4
5 0 6 1
8 8 4 0

输出样例1:

1 3 1 0 6 1 0 
12 

代码

void minpath()
{
    int temp;
	int temp1=10000,temp2=10000;
	dp[0][0]=a[0][0];
    pre[0][0]=a[0][0];
	for(int i=0;i<m;i++)
	{
		for(int j=0;j<n;j++)
		{
			if(j-1>=0)
			{
				temp1=dp[i][j-1]+a[i][j];
			}
			if(i-1>=0)
			{
				temp2=dp[i-1][j]+a[i][j];
			}
			if(temp1!=10000||temp2!=10000)
			{
				if(temp1<temp2)
				{
					dp[i][j]=temp1;
					temp1=10000;
					temp2=10000;
				}
				else
				{
					dp[i][j]=temp2;
					temp1=10000;
					temp2=10000;
				}
			}
		}
	}
	ans=dp[m-1][n-1];
}
void Disppath()
{
    
	int i=m-1;
	int j=n-1;
	int k=0;
	int p[1000];
	p[k++]=a[m-1][n-1];
	while(i!=0||j!=0)
	{
		if(i==0)
		{
			p[k]=a[i][j-1];
			k++;
			j--;
		}
		else if(j==0)
		{
			p[k]=a[i-1][j];
			k++;
			i--;
		}
		else if(dp[i][j-1]<dp[i-1][j])
		{
			p[k]=a[i][j-1];
			k++;
			j--;
		}
		else
		{
			p[k]=a[i-1][j];
			k++;
			i--;
		}
	}
	for(int q=k-1;q>=0;q--)
	{
		printf("%d ",p[q]);
	}
}

7-1 矩阵链相乘问题

矩阵的乘法定义如下:设Am×p的矩阵,Bp×n的矩阵,则AB的乘积为m×n的矩阵,记作C=A**B,其中,矩阵C中的第i行第j列元素c**ij可以表示为:c**ijk=1paik×b**kj=a**i1b1j+a**i2b2j+⋯+aipbp**j.

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

A1,A2,…,A**n为矩阵序列,A**i是阶为P**i−1∗P**i的矩阵(1≤in)。试确定矩阵的乘法顺序,使得计算A1A2…A**n过程中元素相乘的总次数最少。

输入格式:

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

输出格式:

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

输入样例:

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

5
30 35 15 5 10 20

输出样例:

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

11875

代码



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

int N;
int p[1000];
int m[1000][1000];  //m[i][j] 即表示:A(i)A(i+1)(i+2)...A(j); 

int minimum_num(){
	//初始化矩阵,一个矩阵乘法次数为0 
	for(int i = 1; i <= N; i++)
	m[i][i] = 0;
	
	//向二维数组中添加数据,并更新(求最小),
	
	for(int i = N; i >= 1; i--){//这里的i 从 N 先开始,不能从1 先开始,因为一旦从1开始  
							    //那么的话 m[1][N],就在第一次循环便求出,那么m[1][N]在划分 
							    //更新的时候,其中的一些值尚未求出来。如果从N开始,那么最后求得是
								//m[1][N],这样就可以用到了前面已经求出的一些值
		for(int j = i + 1; j <= N; j++){
			//任何一个m[i][j],均可这样先表示出来 即从 i 后面开始划分 
			m[i][j] = m[i][i] + m[i+1][j] + p[i-1]*p[i]*p[j]; 
			
			//划分更新
			for(int k = i + 1;k < j; k++){//这里 k = i + 1 ,表示的是从i+1后面开始划分,
										 //因为上方第一次表示出来的时候,就已经是从 i 后面划分了  
				int temp = m[i][k] + m[k+1][j] + p[i-1] * p[k] * p[j];
				
				if(temp < m[i][j]){
					m[i][j] = temp;
				}
					
			} 		
		}							 
		
	} 
	
	return m[1][N];

}


int main(){
	
	cin >> N;
	
	for(int i = 0; i < N + 1; i++){
		cin >> p[i];
	}
	
	int result = minimum_num();
	
	cout << result;
	
} 

7-2 h0099. 最长公共子序列

给定两个长度分别为N和M的字符串A和B,求既是A的子序列又是B的子序列的字符串长度最长是多少。

输入格式:

第一行包含两个整数N和M。1≤N,M≤1000

第二行包含一个长度为N的字符串,表示字符串A。

第三行包含一个长度为M的字符串,表示字符串B。

字符串均由小写字母构成。

输出格式:

输出一个整数,表示最大长度。

输入样例:

4 5
acbd
abcbd

输出样例:

4

代码

#include<stdio.h>
#include<string.h>
#define N 1010
int i,j,len1,len2,c[N][N];
char x[N],y[N];
int main(){
        scanf("%d %d",&len1,&len2);
       	scanf("%s %s",x,y);
		for(i=0;i<=len1;i++){
			c[i][0]=0;
		}
		for(j=0;j<=len2;j++){
			c[0][j]=0;
		}
		for(i=1;i<=len1;i++){
			for(j=1;j<=len2;j++){
			if(x[i-1]==y[j-1]){
				c[i][j]=c[i-1][j-1]+1;
			}
			else{
				if(c[i-1][j]>c[i][j-1]){
					c[i][j]=c[i][j-1];
				}
				else{
					c[i][j]=c[i][j-1];
				}
			}}}
		printf("%d\n",c[len1][len2]);
	return 0;	
}

7-3 凸多边形最优三角剖分

给定n边凸多边形P,要求确定该凸多边形的三角剖分(将多边形分割成n-2个三角形),使得该三角剖分中诸三角形上权之和为最小。各边弦的权值以由输入数据给出,以无向图的形式表示。三角形的权值等于三条边权值相加。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FeE4wr5m-1672303453672)(算法pta.assets/bc9c1a23-5bf5-47fe-941d-5323707e1de8.jpg)]

输入格式:

第一行输入凸多边形的边数n(3<=n<=8)

第二行起,输入顶点i(1<=i<=n)到顶点j(i<=j<=n)组成的边或弦的权值

输出格式:

最优三角剖分中诸三角形上权值和。

输入样例:

6
0 2 2 3 1 4
0 1 5 2 3
0 2 1 4
0 6 2
0 1
0

输出样例:

24

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-La19cEMj-1672303453672)(算法pta.assets/e876c2b1-4bcf-4ad8-9b88-ec9053fc27c9.jpg)]

代码

/**
	分析: 
	1.凸多边形的三角剖分是将凸多边形分割成互不相交的三角
	  形的弦的集合。
	2.最优三角剖分中诸三角形上权值和:指的是将多边形划分成多个三角形
	                                  其所有的三角形的周长和最小
	3.和矩阵连相乘的思路比较:凸三角形的剖分是通过一个三角形将多边形划分成不同的两部分
							和一个三角形。
		联想矩阵链的递推方程:将其划分成两个不同的子链+这两个自链所构成的矩阵乘法次数
		
		相同点:两种思路一致,
		 不同点:矩阵连计算次数是 pi-1 * pK* pj 
				 多变形是 三边之和	
				 
	4.关于递推方程:t[i][j] = t[i][k] + t[k+1][j] + w(i-1,k,j);
	   这里需要说明的是t[i][j]即表示的是多边形的剖分最小权值和(所有三角形的)
	   比如t[1][6] = t[1][1] + t[2][6] + w(0,1,6),
	   通过点0,1,6将多边形剖分成三部分
	   其中t[1][1] = 0(三角剖分中 只有一条边的是不可以 被剖分成  多边形的 故其权值和为0)
	       t[2][6] 表示的是剩下的多变形,然后再求取它的最小值
	  
	  通过这样的分析:我们可以得知 t[2][6],也就相当于矩阵连相乘问题中的
	  				  子链,那么我就还是可以通过建网格来存储每个多边形的最小权值和
					  来进行求解
	5.本题题解:
	   通过上述分析我们可以得出: 
	  求取凸多边形最优三角剖分 = t[1][n]; 
	 				  			   						  
*/ 

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

int array1[200][200];

//剖分三角形的周长 
int C_triangle(int i,int k,int j){
	return array1[i][k] + array1[k][j] + array1[i][j];
} 

int main(){
	
	int N;
	cin >> N;
	int m[200][200];
	
	//比如有7个顶点(v0,v1..v6),我们数组中存的是边长和弦长 
	for(int i = 0; i < N; i++){
		for(int j  = i; j < N; j++){
			cin >> array1[i][j];
		}
	}
	
//	for(int i = 1; i <= N; i++){
//		for(int j  = 1; j <= N; j++){
//			cout <<  array[i][j] << ' ';
//		}
//		cout << endl;
//	}
	
	for(int i = 0; i <= N; i++){
		m[i][i] = 0;
	}
		
	//开始划分网格和更新
     	
	for(int i = N - 1; i >= 1; i--){
		for(int j = i+1; j <= N - 1; j++){//这里j从i+1开始,因为从i开始每次m[i][i] = 0; 这里j <= N 表示的是这一行到最后比如m[i][N] 
		
			//初始化二维数组 
			m[i][j] = m[i][i] + m[i+1][j] + C_triangle(i-1,i,j);
			
			for(int k = i+1; k < j; k++){
				int temp = m[i][k] + m[k+1][j] + C_triangle(i-1,k,j);
				
				if(temp < m[i][j]){
					 m[i][j] = temp;
				} 
			} 
		}
	} 
	
	
//	for(int i = 1; i < N; i++){
//		for(int j = 1; j < N; j++){
//			cout << m[i][j] << ' ';
//		}
//		cout << endl; 
//	}
	
//	cout << C_triangle(4,5,6); 
	
	cout << m[1][N-1];
	
} 

7-4 多边形游戏

本题目给出一个N个顶点的多边形,每个顶点标记一个数字表示该点的值,每条边标记“+”表示加法或标记“*”表示乘法,这些边从1到N编号。图一所示为一个N=4的多边形。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dovfxJcS-1672303453673)(算法pta.assets/66d6f7bf-c7ef-4c19-bdb8-4b8ea3b10b94.jpg)]

游戏规则

  1. 首先去掉一条边。
  2. 选择一条边E和与E边相连的两个顶点V1、V2,用一个新顶点来替换它们,新顶点的值为边E上标记的运算符对V1和V2上的值进行运算的结果。
  3. 重复步骤2,直到只剩下一个顶点为止,这个顶点上的值即为游戏得分。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gZgThl36-1672303453673)(算法pta.assets/8485b8aa-a033-4559-82b5-082c5290d5da.jpg)]

输入格式:

输入数据有两行,第一行是数字N(3 <= N <= 50),第二行是交叉地给出边上的运算符(字母t表示加法,字母x表示乘法)和顶点的值,以空格分隔。

输出格式:

第一行输出最大得分。

第二行输出游戏第一步移除哪条边可能使得结果分数最高,如果有多种可能则全部输出,每个数字后面有一个空格。

输出格式:

请在这里描述输出格式。例如:对每一组输入,在一行中输出A+B的值。

输入样例:

该样例同图示例子,第一行表示N=4,第二行表示第一条边上符号为加法,第一条边和第二条边中间的顶点值为-7,第二条边上符号为加法,第二条边和第三条边中间的顶点值为4,以此类推。

4
t -7 t 4 x 2 x 5

输出样例:

33
1 2 

代码

#include<bits/stdc++.h>

using namespace std;
const int N = 105;
int f[N][N], g[N][N], n;
char c[N];

int main() {
    cin >> n;
    memset(f, -0x3f, sizeof(f));
    memset(g, 0x3f, sizeof(g));
    for (int i = 1; i <= n; i++) {
        cin >> c[i] >> f[i][i];
        f[i + n][i + n] = g[i][i] = g[i + n][i + n] = f[i][i];
        c[i + n] = c[i];
    }
    for (int len = 2; len <= n; len++)
        for (int l = 1; l <= 2 * n - len + 1; l++) {
            int r = l + len - 1;
            for (int k = l; k <= r - 1; k++) {
                char op = c[k + 1];
                int maxl = f[l][k], minl = g[l][k], maxr = f[k + 1][r], minr = g[k + 1][r];
                if (op == 't')
                    f[l][r] = max(f[l][r], maxl + maxr), g[l][r] = min(g[l][r], minl + minr);
                else {
                    int x1 = maxl * maxr, x2 = maxl * minr, x3 = minl * maxr, x4 = minl * minr;
                    f[l][r] = max(f[l][r], max(max(x1, x2), max(x3, x4)));
                    g[l][r] = min(g[l][r], min(min(x2, x2), min(x3, x4)));
                }
            }
        }
    int res = -0x3f3f3f3f;
    for (int l = 1; l <= n + 1; l++) {
        int r = l + n - 1;
        if (f[l][r] > res)res = f[l][r];
    }
    cout << res << endl;
    for (int l = 1; l <= n; l++) {
        int r = l + n - 1;
        if (f[l][r] == res)
            cout << l << ' ';
    }
    return 0;
}

7-5 流水作业调度

n个作业{1,2,…,n}要在由2台机器M1和M2组成的流水线上完成加工。每个作业加工的顺序都是先在M1上加工,然后在M2上加工。M1和M2加工作业i所需的时间分别为ai和bi。流水作业调度问题要求确定这n个作业的最优加工顺序,使得从第一个作业在机器M1上开始加工,到最后一个作业在机器M2上加工完成所需的时间最少

输入格式:

第一行给出作业个数n(0<n<100)

第二行起的n行,每行两个数ai和bi

输出格式:

两个数字,以空格分隔,分别表示M1机器运行结束的时间和M2机器运行结束的时间。

输入样例:

6
30 80
120 100
50 90
20 60
90 30
110 10

输出样例:

420 430

代码

/**
 	思路:
	1.判断动态规划:
	  首先m1的加工结束时间就是所有的时间和,但m2的加工时间和最小值,
	  求解过程是跳跃性的 所以判定为动态规划
	2.这道题用到了johnson算法,我是拿个例子来理解的,
	  比如:假设再m1上的加工时间为a,在m2上的加工时间为b
	 	  如果作业i和作业j满足min(aj,bi) > min (ai,bj) 则称作业i和j
		  满足johnson法则
		  
		   i在m1和m2上的加工时间为 3,4
		   j在m1和m2上的加工时间为 6,7
		   min(4,6) > min(3,7) 
		  则作业i和j满足johnson法则 
		  
		  若先加工i
		  m2的结束时间 = 3+4+2+7 = 16
		  若先加工j
		  m2的结束时间 = 6+7+4 = 17
		  
		  所以说johnson确定了加工的顺序
	
	3.那么在处理数据的时候我们看到了一对一的摸样,但不能用map容器
	  因为数据当中有重复的部分,这时候我们完全可以用结构体数组来实现
	  同一个下标,但其可以含有多个值,java当中也可以创建一个对象来实现
	  但担心java 的虚拟机可能会超时。。。  
*/ 

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

struct Node{
	int number1; // 在m1上的加工时间
	int number2;//  在m2上的加工时间	
};

//N1集合当中ai的递增排序 
bool sort_N1(Node a,Node b){
	return a.number1 < b.number1;
} 

//N2中按bi的降序排序
bool sort_N2(Node a,Node b){
	return a.number2 > b.number2;
} 

int main(){
	
	int N;
	int a[101];
	int b[101];
	
	Node *stu1 = new Node[101];
	Node *stu2 = new Node[101]; 
	Node *stu3 = new Node[101]; 
	
	cin >> N; 
	
	for(int i = 0; i < N; i++){
		cin >> a[i] >> b[i];
	}
	
//	for(int i = 0; i < N; i++){
//		cout << b[i] << ' ';
//	}

	//开始处理数据在N1的集合当中是作业ai < bi(即在m2上的加工时间大于在m1上的加工时间)
	//N2上的集合是作业的(ai > bi) 
	//还要注意的是在N1上是按照ai的递增排序,在N2上是按照bi的递减排序
	
	int k1 = 0,k2 = 0;
	
	for(int i = 0; i < N; i++){
		//集合N1上 
		if(a[i] < b[i]){
			stu1[k1].number1 = a[i];
			stu1[k1].number2 = b[i];
			k1++;
		}else{
		//集合N2上	
			stu2[k2].number1 = a[i];
			stu2[k2].number2 = b[i];
			k2++;
		}
	}
	
//	for(int i = 0; i < k1; i++){
//		cout << stu1[i].number1 << ' ' << stu1[i].number2 << endl;
//	}	
	
	//对N1集合进行排序(按ai的递增排序) 
	sort(stu1,stu1+k1,sort_N1);
	
	//对N2集合进行排序(按bi的递减顺序进行排序)
	sort(stu2,stu2+k2,sort_N2); 
	
	//将N1和N2集合合并(N1在前,N2在后)
	int k3 = 0;
	for(int i = 0; i < k1; i++){
		 stu3[k3].number1 = stu1[i].number1;
		 stu3[k3].number2 = stu1[i].number2;
		 k3++;
	} 
	
	for(int i = 0; i < k2; i++){
		stu3[k3].number1 = stu2[i].number1;
		stu3[k3].number2 = stu2[i].number2;
		k3++; 
	}
	
	//验证数据 
//	for(int i = 0; i < k3; i++){
//		cout << stu3[i].number1 << ' ';
//	}

	//计算时间m1,m2的结束时间
	int m1,m2;
	m1 = stu3[0].number1;//第一个工作在m1执行完的时间
	m2 = stu3[0].number2 + m1;//第一个工作的总体执行时间
	
	for(int i = 1; i < N; i++){
		m1 = m1 + stu3[i].number1;//第i个工作在m1上的执行时间
		
		if(m1 < m2){//说明m2上的工作还没有完成 
			m2 = m2 + stu3[i].number2;//工作累积 
		}else if(m1 > m2){//说明m2需要等待,因为m1上的工作还未完成 
			m2 = m1 + stu3[i].number2; 
		} 
		 
	} 
	
	cout << m1 << ' ' << m2; 
	
	
} 



//20 30 50 120 90 110
//60 80 90 100 30 10



7-6 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>
#include<string.h>
using namespace std;
int dp[1001][1001]={0};
int volume[1001];//体积
int value[1001];//价值
int main()
{
    int n,v;
    memset(dp,0,sizeof(dp));
    cin>>n;
    cin>>v;
    for(int i=1;i<=n;i++)
    cin>>volume[i]>>value[i];//从1开始
    int maxNum =0;
    for(int i=1;i<=n;i++)
        for(int j=0;j<=v;j++)
        {
            dp[i][j] = dp[i-1][j];//初始填充
            if(j>=volume[i])
            {
                dp[i][j] = max(dp[i-1][j],dp[i-1][j-volume[i]]+value[i]);
                if(dp[i][j]>maxNum)maxNum = dp[i][j];
            }

        }

    cout<<maxNum<<endl;
    return 0;
}


7-7 最优二叉搜索树

S={x1,x2,…,x**n} 是有序集,且 x1<x2<…<x**n,表示有序集S的二叉搜索树利用二叉树的结点来存储有序集中的元素。在该二叉搜索树中搜索一个元素x,结果有两种情形:

  1. 在二叉搜索树的内结点中找到 x=x**i(即找到了实结点),设这种情况的概率为 p**i
  2. 在二叉搜索树的叶节点中确定 x∈(x**i,x**i+1)(即找到了虚结点),设这种情况的概率为 q**i

设根节点的深度为0,存储元素 x**i的结点深度为 c**i,叶节点(x**j,x**j+1)的结点深度为 d**j,在该二叉搜索树中进行一次搜索所需的平均比较次数为 m,那么有公式:

m=∑i=1npi(1+c**i)+∑j=0nqjdj

m又称为二叉搜索树 T 的平均路长,本题的要求是对于有序集 S 及其存取概率分布(q0,p1,q1,…,p**n,q**n),在所有表示有序集 S 的二叉搜索树中找出一颗具有最小平均路长的二叉搜索树。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cZKkD6W6-1672303453673)(算法pta.assets/8aa3a9f7-b010-4b49-8983-3bc460f82765.png)]

输入格式:

第一行给出有序集中的元素数量 n (2<n<10)

第二行给出内结点(实结点)的存取概率分布 p1,…,p**n

第三行给出叶节点(虚结点)的存取概率分布 q0,q1,…,q**n

输出格式:

第一行输出最小 m ,保留两位小数。

第二行先序遍历输出二叉树,对于实结点输出其编号,对于虚结点输出.,每一个符号后有一个空格,若有多个满足要求的二叉树,则输出根节点编号最小的。

输入样例:

3
0.50 0.1 0.05
0.15 0.1 0.05 0.05

输出样例:

1.50
1 . 2 . 3 . . 

代码

#include<bits/stdc++.h>
using namespace std;
const int M = 100;
double C[M][M], W[M][M], p[M], q[M];
int S[M][M];
int n, i, j, k;
void Optimal_BST()
{
 for (i=1;i<=n;i++)
 {
  C[i][i - 1] = 0.0;
  W[i][i - 1] = q[i - 1];
 }
 for (int t=1;t<=n;t++)
 {
  for (i = 1; i <= n - t + 1; i++)
  {
   j = i + t - 1;
   W[i][j] = W[i][j - 1] + p[j] + q[j];
   C[i][j] = C[i][i - 1] + C[i + 1][j];
   S[i][j] = i;
   for (k = i + 1; k < j; k++)
   {
    double tmp = C[i][k - 1] + C[k + 1][j];
    if (tmp<C[i][j] && fabs(tmp - C[i][j])>1E-6)
    {
     C[i][j] = tmp;
     S[i][j] = k;
    }
   }
   C[i][j] += W[i][j];
  }
 }
}
void Construct_Optimal_BST(int i,int j,bool flag)
{
 if (flag == 0)
 {
        printf("%d ",S[i][j]);
  flag = 1;
 }
 int k = S[i][j];
 if (k - 1 < i)
 {
        printf(". ");
 }
 else
 {
        printf("%d ",S[i][k-1]);
  Construct_Optimal_BST(i, k - 1, 1);
 }
 if (k>=j)
 {
        printf(". ");
 }
 else
 {
        printf("%d ",S[k+1][j]);
  Construct_Optimal_BST(k + 1, j, 1);
 }
}
int main()
{

 cin >> n;
 for (i = 1; i <= n; i++)
 {
  cin >> p[i];
 }
 for (i = 0; i <= n; i++)
 {
  cin >> q[i];
 }
 Optimal_BST();
    printf("%.2lf\n",C[1][n]);
 Construct_Optimal_BST(1, n, 0);
 system("pause");
 return 0;
}

7-8 数字三角形

观察下面的数字金字塔。写一个程序查找从最高点到底部任意处结束的路径,使路径经过数字的和最大。每一步可以从当前点走到左下方的点也可以到达右下方的点。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VM8Tx9ZN-1672303453674)(算法pta.assets/2e327c4b-c64e-4022-a486-8531dc9b23bf.jpg)]

在上面的样例中,从13到8到26到15到24的路径产生了最大的和86。

输入格式:

第一个行包含R(1≤ R≤1000),表示行的数目。

后面每行为这个数字金字塔特定行包含的整数。

所有的被供应的整数是非负的且不大于100。

输出格式:

单独的一行,包含那个可能得到的最大的和。

输入样例:

5
13
11 8
12 7  26
6  14 15 8
12 7  13 24 11

输出样例:

86

代码

/**
 	思路:
	 1.分析动态规划:
		 本题当中的求取最终结果依然是跳跃性的,也就是后面的选择有可能要比前面好
		 故判定为动态规划
	 2.本题当中可以将输入的数据输入二维数组当中,将其放置在下三角形当中
	 3.通过分析数据 :我们可以得出一个递推方程
	 就是每次和其上方和左上方所对应的数相加,选取较大的数填写在网格当中
	 
	m[i][j] = max(m[i-1][j]+m[i][j],m[i-1][j-1]+m[i][j])
	
	4.然后就可以在最后一行选取一个较大的结果就是路径和最大的值 
	  
*/ 

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

int main(){
	
	int N;
	cin >> N;
	
	int a[N+1][N+1];
	
	//初始化数组a (升级新技能 不在写for循环进行初始化了)
	memset(a,0,sizeof(a)); 
	
	for(int i = 1; i <= N; i++){
		for(int j = 1; j <= i; j++){
			cin >> a[i][j];
		}
	}
	
	
//	for(int i = 0; i <= N; i++){
//		for(int j = 0; j <= N; j++){
//			cout << a[i][j] << ' ';
//		}
//		cout << endl;
//	}

	//开始建立网格和更新
	
	for(int i = 1; i <= N; i++){
		for(int j = 1; j <= N; j++){
				
			a[i][j] = max(a[i][j] + a[i-1][j],a[i][j]+a[i-1][j-1]);
	
		}
	} 
	
	int maxx = 0;
	
	for(int j = 1; j <= N; j++){
		maxx = max(maxx,a[N][j]);
	}
	
	cout << maxx;

} 

7-9 乘法表

定义于字母表 ∑={a,b,c} 上的乘法表如下。

abc
abba
bcba
cacc

依此乘法表,对任一定义于∑ 上的字符串,适当加括号后,得到一个表达式。例如,对于字符串 x=bbbba ,它的一个加括号表达式为(b(bb))(ba) 。依乘法表,该表达式的值为 a 。试设计一个动态规划算法,对任一定义于 ∑ 上的字符串 x=x1x2…x**n ,计算有多少种不同的加括号方式,使由 x 导出的加括号表达式的值为 a

输入格式:

一个字符串,长度不大于20

输出格式:

加括号的方法数量

输入样例:

bbbba

输出样例:

6

代码

#include <iostream>
#include <string>
using namespace std;
const int maxn=50;
int result[maxn][maxn][3]={0};
int main()
{
	string str;
	cin>>str;
	int n=str.size();
	//int result[n+1][n+1][3];
	
	for(int i=1;i<=n;++i)//相当于是求边界的值
	//毕竟动态规划是从低向上的  所以先算边界 
	{
		if(str[i-1]=='a') result[i][i][0]=1;
		else result[i][i][0]=0;
		if(str[i-1]=='b') result[i][i][1]=1;
		else result[i][i][1]=0;
		if(str[i-1]=='c') result[i][i][2]=1;
		else result[i][i][2]=0;
	}       
	
	for(int r=2;r<=n;++r)//区间长度                                       
		for(int i=1;i<=n-r+1;++i)//区间起始 
		{
			int j=i+r-1;	
			for (int k = i; k<j; k++)//k表示分割的位置 
                {//根据题目中的表,计算加括号法
                    result[i][j][0] += result[i][k][0] * result[k + 1][j][2] + result[i][k][1] * result[k + 1][j][2] + result[i][k][2] * result[k + 1][j][0];
                    result[i][j][1] += result[i][k][0] * result[k + 1][j][0] + result[i][k][0] * result[k + 1][j][1] + result[i][k][1] * result[k + 1][j][1];
                    result[i][j][2] += result[i][k][1] * result[k + 1][j][0] + result[i][k][2] * result[k + 1][j][1] + result[i][k][2] * result[k + 1][j][2];
                }
    	}
    	
     cout << result[1][n][0] << endl;
	return 0;
  
 } 

7-10 石子合并

在一个圆形操场的四周摆放 N 堆石子,现要将石子有次序地合并成一堆.规定每次只能选相邻的 2 堆合并成新的一堆,并将新的一堆的石子数,记为该次合并的得分。

试设计出一个算法,计算出将 N 堆石子合并成 1 堆的最小得分和最大得分。

输入格式:

数据的第 1 行是正整数 N ,表示有 N 堆石子。

第 2 行有 N 个整数,第 i 个整数 a**i 表示第 i 堆石子的个数。

输出格式:

输出共 2 行,第 1 行为最小得分,第 2 行为最大得分。

输入样例:

4
4 5 9 4

输出样例:

43
54

代码

#include<bits/stdc++.h>
const int N=410, INF=0x3f3f3f3f;
using namespace std;  

int main()
{
	int n;
	cin >> n;
	int a[N];            //记录输入的每堆石子的数量
	int sum[N];          //计算前缀和
	int Min[N][N];     //表示把从l到r合并成一堆的得分总和最小值
	int Max[N][N];     //表示把从l到r合并成一堆的得分总和最大值

	memset(Min,INF,sizeof Min);      //数组初始化 
	memset(Max,0,sizeof Max);
	for(int i=1;i<=n;i++)
	{
		cin >> a[i];
		a[i+n]=a[i];     //复制一遍区间	
	} 
	
	for(int i=1;i<=2*n;i++)
	{
		Min[i][i]=0;     //初始化 
		Max[i][i]=0;	//初始化
		sum[i]=sum[i-1]+a[i];    //计算前缀和 
	} 
	
	for(int len=2;len<=n;len++)      //阶段:枚举区间长度,len记录相邻合并的石子数 
	{
		for(int l=1;l+len-1<=2*n;l++)      //状态:枚举区间起点
		{
			int r=l+len-1;         //区间终点
	 		for(int k=l; k<r; k++)          //决策:枚举分割点
			{ 
	 			Min[l][r]=min(Min[l][r],Min[l][k]+Min[k+1][r]+sum[r]-sum[l-1]);
	 			Max[l][r]=max(Max[l][r],Max[l][k]+Max[k+1][r]+sum[r]-sum[l-1]);
	 		}
		} 
	} 

	int minn=INF, maxx=0;
	for(int i=1;i<=n;i++)
	{
		minn=min(minn,Min[i][i+n-1]);                 //Min[1,n],f[2,n+1]..f[n,2n-1]
		maxx=max(maxx,Max[i][i+n-1]);                 //Max[1,n],g[2,n+1]..g[n,2n-1]
	}
	cout << minn << endl << maxx;
	return 0;	
}

7-11 租用游艇问题

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

长江游艇俱乐部在长江上设置了n个游艇出租站1,2,…,n。游客可在这些游艇出租站租用游艇,并在下游的任何一个游艇出租站归还游艇。游艇出租站i到游艇出租站j之间的租金为r(i,j),1<=i<j<=n。试设计一个算法,计算出从游艇出租站1 到游艇出租站n所需的最少租金。

输入格式:

第1 行中有1 个正整数n(n<=200),表示有n个游艇出租站。接下来的第1到第n-1 行,第i行表示第i站到第i+1站,第i+2站, … , 第n站的租金。

输出格式:

输出从游艇出租站1 到游艇出租站n所需的最少租金。

输入样例:

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

3
5 15
7

输出样例:

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

12

代码

#include <iostream>
using namespace std;
int a[10001][10001], k;
int main()
{
	int n;
	cin >> n;
	for(int i=1; i<=n; i++){
		a[i][i]=0;
	}
	for(int i=1; i<n; i++){  
		for(int j=i+1; j<=n; j++){
			cin >> a[i][j];
		}
	}
	for(int i=2; i<=n; i++){
		for(int j=i+1; j<=n; j++){
			k=j-i;   //k从1开始
			for(int p=k; p<j; p++){
				if(a[k][j] > a[k][p]+a[p][j]){
					a[k][j]=a[k][p]+a[p][j];
				}
			} 
		}
	}
	cout << a[1][n];
}

6贪心算法练习题

7-1 活动选择问题

假定一个有n个活动(activity)的集合S={a1,a2,…,a**n},这些活动使用同一个资源(例如同一个阶梯教室),而这个资源在某个时刻只能供一个活动使用。每个活动a**i都有一个开始时间s**i和一个结束时间f**i,其中0<=s**i<f**i<=32767。如果被选中,任务a**i发生在半开时间区间[s**i,f**i)期间。如果两个活动a**ia**j满足[s**i,f**i)和[s**j,f**j)不重叠,则称它们是兼容的。也就说,若s**i>=f**js**j>=f**i,则a**ia**j是兼容的。在活动选择问题中,我们希望选出一个最大兼容活动集。

输入格式:

第一行一个整数n(n≤1000);

接下来的n行,每行两个整数,第一个s**i,第二个是f**i(0<=s**i<f**i<=32767)。

输出格式:

输出最多能安排的活动个数。

输入样例:

11
3 5
1 4
12 14
8 12
0 6
8 11
6 10
5 7
3 8
5 9
2 13

输出样例:

4

样例解释:

安排的4个活动为1 4, 5 7, 8 11和12 14。

代码

#include <stdio.h>
#include <string.h>

void swap(int *a, int *b)
{
    int temp = *a;
    *a = *b;
    *b = temp;
}
int main()
{
    int n;
    int s[50000], f[50000];

    scanf("%d", &n);

    for (int i = 0; i < n; i++)
        scanf("%d %d", &s[i], &f[i]);

    for (int i = 0; i < n - 1; i++)
    {
        for (int j = 0; j < n - i - 1; j++)
        {
            if (f[j] > f[j + 1])
            {
                swap(&f[j], &f[j + 1]);
                swap(&s[j], &s[j + 1]);
            }
        }
    }

    int cnt = 1;
    int now = f[0];
    for (int i = 1; i < n; i++)
    {
        if (s[i] >= now)
        {
            now = f[i];
            cnt++;
        }
    }
    printf("%d\n", cnt);
    return 0;
}

7-2 单源最短路径

请编写程序求给定正权有向图的单源最短路径长度。图中包含n个顶点,编号为0至n-1,以顶点0作为源点。

输入格式:

输入第一行为两个正整数n和e,分别表示图的顶点数和边数,其中n不超过20000,e不超过1000。接下来e行表示每条边的信息,每行为3个非负整数a、b、c,其中a和b表示该边的端点编号,c表示权值。各边并非按端点编号顺序排列。

输出格式:

输出为一行整数,为按顶点编号顺序排列的源点0到各顶点的最短路径长度(不含源点到源点),每个整数后一个空格。如源点到某顶点无最短路径,则不输出该条路径长度。

输入样例:

4 4
0 1 1
0 3 1
1 3 1
2 0 1

输出样例:

1 1 

代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>

#define max 20005
struct Node
{
    int end;
    int value;
    int next;
} node[max];

int arr[max], n, m, cnt = 0;

void calculate(int start, int end, int value)
{
    cnt++;
    node[cnt].end = end;
    node[cnt].value = value;
    node[cnt].next = arr[start];
    arr[start] = cnt;
}

void Dijkstra()
{
    int dist[max];
    for (int i = 0; i < 20005; i++)
        dist[i] = INT_MAX;
    int visit[max] = {0};

    for (int i = arr[0]; i != 0; i = node[i].next)
        dist[node[i].end] = node[i].value;
    visit[0] = 1;

    while (1)
    {
        int m = -1;
        int min = INT_MAX;
        for (int i = 0; i < n; i++)
        {
            if (visit[i] != 1 && dist[i] < min)
            {
                min = dist[i];
                m = i;
            }
        }
        if (m == -1)
            break;
        visit[m] = 1;
        for (int i = arr[m]; i != 0; i = node[i].next)
        {
            if (visit[node[i].end] != 1 && min + node[i].value < dist[node[i].end])
            {
                dist[node[i].end] = min + node[i].value;
            }
        }
    }
    for (int i = 0; i < n; i++)
    {
        if (dist[i] != INT_MAX)
        {
            printf("%d ", dist[i]);
        }
    }
}

int main()
{
    memset(arr, 0, sizeof(arr));
    scanf("%d %d", &n, &m);

    for (int i = 0; i < m; i++)
    {
        int start, end, value;
        scanf("%d %d %d", &start, &end, &value);
        calculate(start, end, value);
    }
    Dijkstra();
    return 0;
}

7-3 最小生成树-kruskal

题目给出一个无向连通图,要求求出其最小生成树的权值。

温馨提示:本题请使用kruskal最小生成树算法。

输入格式:

第一行包含两个整数 N(1<=N<=1x106),M(1<=M<=1x106) 表示该图共有 N 个结点和 M 条无向边。

接下来 M 行每行包含三个整数 X**i,Y**i,Z**i ,表示有一条长度为 Z**i 的无向边连接结点 X**i,Y**i

输出格式:

输出一个整数表示最小生成树的各边的长度之和。

输入样例:

4 5
1 2 2
1 3 2
1 4 3
2 3 4
3 4 3

输出样例:

7

代码

#include <iostream>
#include <algorithm>
using namespace std;

#define max 1000001

struct Node
{
    int x, y, z;
} node[max];

int n, m;
int tree[max];

bool cmp(const Node &a, const Node &b)
{
    return a.z < b.z;
}
int find(int x)
{
    if (tree[x] != x)
        return tree[x] = find(tree[x]);
    return tree[x];
}
int kruskal(struct Node a[], int N, int M)
{
    for (int i = 0; i < 1000; i++)
        ;
    sort(a, a + m, cmp);
    for (int i = 1; i <= N; i++)
        tree[i] = i;
    int sum = 0;
    for (int i = 0; i < M; i++)
    {
        int x = a[i].x;
        int y = a[i].y;
        int z = a[i].z;
        if (find(x) != find(y))
        {
            tree[find(x)] = find(y);
            sum += z;
        }
    }
    return sum;
}
int main()
{
    for (int i = 0; i < 1000; i++)
        ;
    scanf("%d %d", &n, &m);
    for (int i = 0; i < m; i++)
        scanf("%d %d %d", &node[i].x, &node[i].y, &node[i].z);

    printf("%d\n", kruskal(node, n, m));
    return 0;
}

7-4 最小生成树-prim

题目给出一个无向连通图,要求求出其最小生成树的权值。

温馨提示:本题请使用prim最小生成树算法。

输入格式:

第一行包含两个整数 N(1<=N<=1x104),M(1<=M<=2x106) 表示该图共有 N 个结点和 M 条无向边。

接下来 M 行每行包含三个整数 X**i,Y**i,Z**i ,表示有一条长度为 Z**i 的无向边连接结点 X**i,Y**i

输出格式:

输出一个整数表示最小生成树的各边的长度之和。

输入样例:

4 5
1 2 2
1 3 2
1 4 3
2 3 4
3 4 3

输出样例:

7

代码

#include <iostream>
#include <algorithm>
using namespace std;

#define max 100000001

struct Node
{
    int x, y, z;
} node[max];

int n, m;
int tree[max];

bool cmpFunc(const Node &a, const Node &b)
{
     for (int i = 0; i < 10000; i++)
        ;
    return a.z < b.z;
}
int find(int x)
{
    if (tree[x] != x)
        return tree[x] = find(tree[x]);
    return tree[x];
}
int Prim(struct Node a[], int N, int M)
{
    for (int i = 0; i < 10000; i++)
        ;
    sort(a, a + m, cmpFunc);
    for (int i = 1; i <= N; i++)
        tree[i] = i;
    int sum = 0;
    for (int i = 0; i < M; i++)
    {
        int x = a[i].x;
        int y = a[i].y;
        int z = a[i].z;
        if (find(x) != find(y))
        {
            tree[find(x)] = find(y);
            sum += z;
        }
    }
    return sum;
}
int main()
{
    for (int i = 0; i < 1000; i++)
        ;
    scanf("%d %d", &n, &m);
    for (int i = 0; i < m; i++)
        scanf("%d %d %d", &node[i].x, &node[i].y, &node[i].z);

    printf("%d\n", Prim(node, n, m));
    return 0;
}

7-5 汽车加油问题

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

一辆汽车加满油后可行驶 n公里。旅途中有若干个加油站。设计一个有效算法,指出应
在哪些加油站停靠加油,使沿途加油次数最少。

输入格式:

第一行有 2 个正整数n和 k(k<=1000 ),表示汽车加满油后可行驶n公里,且旅途中有 k个加油站。
第二行有 k+1 个整数,表示第 k 个加油站与第k-1 个加油站之间的距离。
第 0 个加油站表示出发地,汽车已加满油。
第 k+1 个加油站表示目的地。

输出格式:

输出最少加油次数。如果无法到达目的地,则输出“No Solution!”。

输入样例:

7 7
1 2 3 4 5 1 6 6 

输出样例:

4

代码

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <limits.h>

int main()
{
    int n, k;
    scanf("%d %d", &n, &k);
    int arr[k + 1];
    for (int i = 0; i < k + 1; i++)
        scanf("%d", &arr[i]);

    if (n < arr[k])
        printf("No Solution!\n");
    else
    {
        int sum = 0, cnt = 0;
        for (int i = 0; i < k + 1; i++)
        {
            sum += arr[i];
            if (sum > n)
            {
                sum = arr[i];
                cnt++;
            }
        }
        printf("%d\n", cnt);
    }
    return 0;
}

7-6 区间覆盖

x1,x2,…,x**n 是实直线上的n个点。用固定长度的闭区间覆盖这n个点,至少需要多少个这样的固定长度闭区间?

输入格式:

第1行有2个正整数n(n<50)和k,表示有n个点,且固定长度闭区间的长度为k。

接下来的1行中有n个整数 a**i(−2000<a**i<2000) ,表示n个点在实直线上的坐标(可能相同)。

输出格式:

最少区间数。

输入样例:

7 3
1 2 3 4 5 -2 6

输出样例:

3

代码

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <limits.h>

int cmpFunc(const void *a, const void *b)
{
    return (*(int *)a - *(int *)b);
}

int main()
{
    int n, k, m[100];
    scanf("%d %d", &n, &k);
    for (int i = 0; i < n; i++)
        scanf("%d", &m[i]);
    qsort(m, n, sizeof(int), cmpFunc);

    int mid = m[0], cnt = 0;
    for (int i = 1; i < n; i++)
    {
        if (m[i] - mid >= k)
        {
            mid = m[i];
            cnt++;
        }
        if (i != n - 1 && m[n - 1] - mid < k)
        {
            cnt++;
            break;
        }
    }
    printf("%d\n", cnt);

    return 0;
}

7-7 硬币找钱问题

设有6 种不同面值的硬币,各硬币的面值分别为5 分,1 角,2 角,5 角,1 元,2元。现要用这些面值的硬币来购物。在购物中希望使用最少个数硬币。例如,1 次购物需要付款0.55 元,如果没有5 角的硬币,只好用22角+11角+1*5分 共4 枚硬币来付款。

对于给定的各种面值的硬币个数和付款金额,计算使用硬币个数最少的交易方案。

输入格式:

输入数据有若干组,第一行给出一个整数n表示输入数据的组数。

以下n行每一行有6 个整数和1个有2 位小数的实数。分别表示可以使用的各种面值的硬币个数和付款金额。

输出格式:

输出每组数据的最少硬币个数。如果不可能完成交易,则输出“impossible”。

输入样例:

2
2 4 2 2 1 0 0.95
2 4 2 0 1 0 0.55

输出样例:

4
4

代码

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <limits.h>

int cmpFunc(const void *a, const void *b)
{
    return (*(int *)a - *(int *)b);
}

int main()
{
    int n, k, m[100];
    double price;
    scanf("%d", &n);
    int coin[6] = {5, 10, 20, 50, 100, 200};
    while (n--)
    {
        int cnt = 0;
        double sum = 0;
        for (int i = 0; i < 6; i++)
        {
            scanf("%d", &m[i]);
            sum += m[i] * 1.0 * coin[i];
        }
        scanf("%lf", &price);
        price *= 100;
        if (sum < price)
        {
            printf("impossible\n");
            continue;
        }

        for (int i = 5; i >= 0; i--)
        {
            double res = price / coin[i];
            // printf("%d : %d\n", (int)res, m[i]);
            int t = (int)(price / coin[i]) < m[i] ? (int)(price / coin[i]) : m[i];
            price -= t * coin[i];
            cnt += t;
        }
        printf("%d\n", cnt);
    }
    return 0;
}

7-8 最优服务次序问题

设有n 个顾客同时等待一项服务。顾客i需要的服务时间为 t**i(1<=i<=n) 。应如何安排n个顾客的服务次序才能使平均等待时间达到最小?平均等待时间是n 个顾客等待服务时间的总和除以n。

输入格式:

第一行是正整数n(1<n<1000),表示有n 个顾客。接下来的1行中,有n个正整数,表示n个顾客需要的服务时间。

输出格式:

计算出的最小平均等待时间,保留两位小数。

输入样例:

10
56 12 1 99 1000 234 33 55 99 812

输出样例:

291.90

代码

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <limits.h>

int cmpFunc(const void *a, const void *b)
{
    return (*(int *)a - *(int *)b);
}

int main()
{
    int n, arr[1005], sum = 0, res = 0;
    scanf("%d", &n);
    for (int i = 0; i < n; i++)
        scanf("%d", &arr[i]);
    qsort(arr, n, sizeof(int), cmpFunc);
    for (int i = 0; i < n - 1; i++)
    {
        sum += arr[i];
        res += sum;
    }
    printf("%.2lf\n", res * 1.0 / n);
    return 0;
}

7-9 删数问题

有一个长度为n(n <= 240)的正整数,从中取出k(k < n)个数,使剩余的数保持原来的次序不变,求这个正整数经过删数之后最小是多少。

输入格式:

n和k

输出格式:

一个数字,表示这个正整数经过删数之后的最小值。

输入样例:

178543 4

输出样例:

13

代码

#include <stdio.h>
#include <string.h>

int main()
{
    char a[500];
    int n;
    scanf("%s %d", a, &n);

    int len = strlen(a);

    for (int i = 0; i < n; i++)
    {
        for (int j = 0; j < len - 1; j++)
        {
            if (a[j] > a[j + 1])
            {
                for (int k = j; k < len - 1; k++)
                    a[k] = a[k + 1];
                break;
            }
        }
        len--;
    }
    int res = 0;
    for (int i = 0; i < len; i++)
    {
        if (res != 0)
            printf("%c", a[i]);
        else
        {
            if (a[i] != '0')
            {
                printf("%c", a[i]);
                res = 1;
            }
        }
    }
    return 0;
}

7回溯法练习题

判断

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OB21dx06-1672303453675)(算法pta.assets/image-20221228212229868.png)]

选择

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nqPF6030-1672303453675)(算法pta.assets/image-20221228212300022.png)]

7-1 装载问题

有一批共n(1<n<20)个集装箱要装上2艘载重量分别为 c1 和 c2 的轮船,其中集装箱 i 的重量为 w**i ,且 ∑i=1nwi<=c1+c2 。装载问题要求确定可将这n个集装箱装上这2艘轮船的合理的装载方案。

  • 例如,当

    n=3

    c1=c2=50

    ,且

    w=[10,40,40]

    时有四种装载方案

    1. 将集装箱1和2装上第一艘轮船,而将集装箱3装上第二艘轮船;
    2. 将集装箱1和2装上第二艘轮船,而将集装箱3装上第一艘轮船;
    3. 将集装箱1和3装上第一艘轮船,而将集装箱2装上第二艘轮船;
    4. 将集装箱1和3装上第二艘轮船,而将集装箱2装上第一艘轮船;
  • 如果 w=[20,40,40] ,则无法将这3个集装箱都装上轮船。

输入格式:

第一行给出 nc1,c2

第二行给出 w**i

输出格式:

输出所有可行的方案数量

输入样例1:

3 50 50
10 40 40

输出样例1:

4

输入样例2:

3 50 50
20 40 40

输出样例2:

0

代码

#include <stdio.h>
#include <math.h>
#include <string.h>
#include <stdlib.h>
#include <limits.h>

int n;
int x[1000];
int tree[10000][20];
int path[10000][20];
void buildTree()
{
    for (int i = 0; i < n; i++)
    {
        for (int j = 0; j < pow(2, i + 1); j += 2)
        {
            tree[i][j] = 1;
            tree[i][j + 1] = 2;
        }
    }
}

int main()
{
    int m1, m2;
    scanf("%d", &n);
    int w[n];
    scanf("%d %d", &m1, &m2);
    for (int i = 0; i < n; i++)
        scanf("%d", &w[i]);

    buildTree();
    int cnt = 0, index = 0;
    for (int i = 0; i < n; i++)
    {
        int jLen = pow(2, i + 1);
        for (int j = 0; j < jLen; j++)
        {
            int tLen = pow(2, n) / jLen;
            for (int t = 0; t < tLen; t++)
            {
                path[t + j * tLen][index] = tree[i][j];
            }
        }
        index++;
    }
    for (int i = 0; i < pow(2, n); i++)
    {
        int t1 = 0, t2 = 0, flag = 0;
        for (int j = 0; j < n; j++)
        {
            if (path[i][j] == 1)
                t1 += w[j];
            else if (path[i][j] == 2)
                t2 += w[j];
        }
        if (t1 > m1 || t2 > m2)
            flag = 1;
        if (flag == 0)
            cnt++;
    }
    printf("%d\n", cnt);
    return 0;
}

7-2 批处理作业调度

给定n个作业的集合 J=(J1,J2,…,J**n) 。每个作业 J**i 都有两项任务分别在两台机器上完成。每个作业必须先由机器1处理,再由机器2处理。作业 J**i 需要机器 j 的处理时间为 t**ji(i=1,2,…,n;j=1,2) 。对于一个确定的作业调度,设 F**ji 是作业 i 在机器 j 上完成处理的时间,则所有作业在机器2上完成处理的时间和 f=∑i=1n**F2i 称为该作业调度的完成时间和。

批处理作业调度问题要求,对于给定的n个作业,制定最佳作业调度方案,使其完成时间和达到最小。

输入格式:

第一行输入作业个数n。

第二行输入各任务在机器一上的完成时间。

第三行输入各任务在机器二上的完成时间。

输出格式:

最短完成时间和

输入样例:

3
2 3 2
1 1 3

输出样例:

18

代码

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <limits.h>

int n;
int min = INT_MAX;
int t1 = 1, count = 0;
int m1[1000], m2[1000];
int flag[1000] = {0};
int path[1000][1000];
int x[1000];

void backtrack(int cnt)
{
    if (cnt > n)
    {
        for (int i = 1; i <= n; i++)
            path[t1][i] = x[i];
        t1++;
        count++;
    }
    else
    {
        for (int i = 1; i <= n; i++)
        {
            if (flag[i] == 1)
                continue;
            flag[i] = 1;
            x[cnt] = i;
            backtrack(cnt + 1);
            flag[i] = 0;
        }
    }
}

int main()
{
    scanf("%d", &n);
    for (int i = 1; i <= n; i++)
        scanf("%d", &m1[i]);
    for (int i = 1; i <= n; i++)
        scanf("%d", &m2[i]);
    backtrack(1);
    for (int i = 1; i <= count; i++)
    {
        int last = 0, time = 0, endtime = 0;
        for (int j = 1; j <= n; j++)
        {
            if (last < m1[path[i][j]])
            {
                endtime += m1[path[i][j]] + m2[path[i][j]] - last;
            }
            else
            {
                endtime += m2[path[i][j]];
            }
            time += endtime;
            last = m2[path[i][j]];
        }
        if (min > time)
            min = time;
    }
    printf("%d\n", min);

    return 0;
}

7-3 符号三角形

下图是14个“+”和14个“-”组成的符号三角形。2个同号下面都是“+”,2个异号下面都是“-”。

在一般情况下,符号三角形的第一行有n个符号。符号三角形问题要求对于给定的n,计算有多少个不同的符号三角形,使其所含的“+”和“-”的个数相同。

  +   +   _   +   _   +   +

      +  _   _   _   _   +

       _   +  +  +  _

        _   +   +  _

         _   +  _

          _  _

             +

输入格式:

第一行符号个数n

输出格式:

符合要求的三角形个数

输入样例:

4

输出样例:

6

代码

#include <iostream>
using namespace std;

int n, half, ans, sum;
int s[100][100];

void DFS(int t, int sum)
{
    for(int i=0; i<1000; i++);
    if (sum > half || t * (t - 1) / 2 - sum > half) //已经算出的子树不满足,剪枝
        return;
    if (t > n)
    {
        ans++;
        return;
    }
    for (int i = 0; i < 2; i++)
    {
        s[1][t] = i;
        for (int j = 2; j <= t; j++)
        {
            s[j][t - j + 1] = s[j - 1][t - j + 1] ^ s[j - 1][t - j + 2]; //递推子树
            sum += s[j][t - j + 1];
        }
        DFS(t + 1, sum + i);
        for (int j = 2; j <= t; j++) //回溯时取消上一次的赋值
            sum -= s[j][t - j + 1];
    }
}

int main()
{
    //n为输入的第一行的个数
    cin >> n;
    ans = 0;
    sum = 0;
    half = n * (n + 1) / 2;

    if (half % 2 != 0) //总数是奇数直接判0
        cout << 0 << endl;
    else
    {
        half /= 2;
        DFS(1, 0);
        cout << ans << endl;
    }

    return 0;
}

7-4 N皇后

在N*N的方格棋盘放置了N个皇后,使得它们不相互攻击(即任意2个皇后不允许处在同一排,同一列,也不允许处在与棋盘边框成45角的斜线上。
你的任务是,对于给定的N,求出有多少种合法的放置方法

输入格式:

共有若干行,每行一个正整数N≤10,表示棋盘和皇后的数量;

输出格式:

共有若干行,每行一个正整数,表示对应输入行的皇后的不同放置数量

输入样例:

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

1
8
5

输出样例:

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

1
92
10

代码

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int n, cnt = 0;
int arr[100]; // 存的是每一行皇后的列数

void getQueen(int row)
{
    if (row == n)
    {
        cnt++;
        return;
    }
    // 列数
    for (int i = 0; i < n; i++)
    {
        int flag = 0;
        // 已有皇后的行
        for (int j = 0; j < row; j++)
        {
            // 排除 同一列 || 斜线 上的数据
            if (arr[j] == i || (abs(arr[j] - i) == abs(row - j)))
            {
                flag = 1;
                break;
            }
        }
        if (flag == 0)
        {
            arr[row] = i;
            getQueen(row + 1);
        }
    }
}
int main()
{
    while ((scanf("%d", &n)) != EOF)
    {
        cnt = 0;
        getQueen(0);
        printf("%d\n", cnt);
    }
}

7-5 幂集(回溯法)

有一个含n个数的数组a,所有元素均不相同,设计一个算法求其所有子集(幂集)。
例如:1 2 3的幂集{}、{3}、{2}、{2,3}、{1}、{1,3}、{1,2}、{1,2,3}

输入格式:

第一行输入元素个数n,再依次输入n个数。

输出格式:

输出子集数

输入样例1:

3
1 2 3

输出样例1:

8

代码

/*有一个含n个数的数组a,所有元素均不相同,设计一个算法求其所有子集(幂集)。
第一行输入元素个数n,再依次输入n个数。
输出子集数*/
#include<stdio.h>
#include<math.h>
int main()
{
    int n;
    scanf("%d",&n);
    int i;
    int a[n];
    int cnt=0;
    for(i=0;i<n;i++){
        scanf("%d",&a[i]);
    }
    cnt=pow(2,n);
    printf("%d",cnt);
    return 0;
}

8分支限界练习题

选择

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-p2WsyUdA-1672303453675)(算法pta.assets/image-20221228212825397-167223410730611.png)]

填空

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IFNUYiEr-1672303453676)(算法pta.assets/image-20221228212902256-167223414436212.png)]

6-1 求解任务分配问题(分枝限界法)

有n(n≥1)个任务需要分配给n个人执行,每个任务只能分配给一个人,每个人只能执行一个任务。第i个人执行第j个任务的成本是c[i][j](1≤i,j≤n)。求出总成本最小的分配方案。

函数接口定义:

void bound(NodeType &e);//求结点e的限界值
void bfs();        //求解任务分配

裁判测试程序样例:

#include <stdio.h>
#include <string.h>
#include <queue>
#include <iostream>
using namespace std;
#define INF 0x3f3f3f3f            //定义∞
#define MAXN 21
int n;
int c[MAXN][MAXN];    //下标0的元素不用
int bestx[MAXN];                //最优分配方案
int mincost=INF;                //最小成本
int total=1;                    //结点个数累计
struct NodeType                    //队列结点类型
{
    int no;                        //结点编号
    int i;                        //人员编号
    int x[MAXN];                //x[i]为人员i分配的任务编号
    bool worker[MAXN];            //worker[i]=true表示任务i已经分配
    int cost;                    //已经分配任务所需要的成本
    int lb;                        //下界
    bool operator<(const NodeType &s) const    //重载<关系函数
    {
        return lb>s.lb;
    }
};

void bound(NodeType &e);            //求结点e的限界值
void bfs();                    //求解任务分配


int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
      for(int j=1;j<=n;j++)
          scanf("%d",&c[i][j]);    
    bfs();
    for (int k=1;k<=n;k++)
        printf("%d %d\n",k,bestx[k]);
    printf("%d",mincost);
    return 0;
}

/* 请在这里填写答案 */

输入格式:

第1行输入人数n。接下来输入n行,每行n个数,表示第i个人执行第j个任务的成本(w人员及任务编号从1到n)。

输出格式:

先输入分配方案:按人员编号递增顺序依次输出n行,每行两个数,分别表示人员编号和任务编号。最后再输出总成本

输入样例:

4
9 2 7 8
6 4 3 7
5 8 1 8
7 6 9 4

输出样例:

1 2
2 1
3 3
4 4
13

代码

void bound(NodeType &e)
{
    int minsum=0;
    for(int i1=e.i+1;i1<=n;i1++)
    {
        int minc=INF;
        for(int j1=1;j1<=n;j1++)
            if(e.worker[j1]==false&&c[i1][j1]<minc)//寻找每一列的最小值
                minc=c[i1][j1];
        minsum+=minc;
    }
    e.lb=e.cost+minsum;
}

void bfs()
{
    int j;
    NodeType e,e1;
    priority_queue<NodeType> qu;
    memset(e.x,0,sizeof(e.x));
    memset(e.worker,0,sizeof(e.worker));

    e.i=0;
    e.cost=0;
    bound(e);
    e.no=total++;
    qu.push(e);

    while(!qu.empty())
    {
        e=qu.top();
        qu.pop();
        if(e.i==n)
        {
            if(e.cost<mincost)
            {
                mincost=e.cost;
                for(j=1;j<=n;j++)
                    bestx[j]=e.x[j];
            }
        }

        e1.i=e.i+1;
        for(j=1;j<=n;j++)
        {
            if(e.worker[j])
                continue;
            for(int i1=1;i1<=n;i1++)
                e1.x[i1]=e.x[i1];
            e1.x[e1.i]=j;
            for(int i2=1;i2<=n;i2++)
                e1.worker[i2]=e.worker[i2];
            e1.worker[j]=true;
            e1.cost=e.cost+c[e1.i][j];
            bound(e1);
            e1.no=total++;
            if(e1.lb<=mincost)
                qu.push(e1);
        }
    }
}

7-1 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 <stdio.h>
#include <string.h>
#include <stdlib.h>

int n;
int c;
int cnt;
int w[105];
int v[105];
int x[105];
int path[10000][1000];

void func(int level, int num)
{
    if (level == n)
    {
        x[n] = num;
        for (int i = 1; i <= n; i++)
            path[cnt][i] = x[i];
        cnt++;
    }
    else
    {
        if (level != 0)
            x[level] = num;
        func(level + 1, 0);
        func(level + 1, 1);
    }
}

int main()
{
    int max = -1;
    scanf("%d %d", &n, &c);
    for (int i = 1; i <= n; i++)
        scanf("%d %d", &w[i], &v[i]);
    for (int i = 1; i <= n; i++)
        for (int j = 0; j <= c; j++)
        {
            path[i][j] = path[i - 1][j];
            if (j >= w[i])
            {
                path[i][j] = path[i - 1][j] > path[i - 1][j - w[i]] + v[i] ? path[i - 1][j] : path[i - 1][j - w[i]] + v[i];
                if (path[i][j] > max)
                    max = path[i][j];
            }
        }
    printf("%d\n", max);
    return 0;
}

7-2 旅行售货员

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

输入格式:

第一行为城市数n

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

输出格式:

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

输入样例:

3
0 2 1
1 0 2
2 1 0

输出样例:

3

代码

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int n;
int c;
int cnt;
int x[105];
int flag[100];
int ins[100][100];
int path[10000][100];

void func(int level)
{
    if (level == n)
    {
        for (int i = 0; i < n; i++)
            path[cnt][i] = x[i];
        cnt++;
    }
    else
    {
        for (int i = 1; i <= n; i++)
        {
            if (flag[i] == 1)
                continue;
            flag[i] = 1;
            x[level] = i;
            func(level + 1);
            flag[i] = 0;
        }
    }
}

int main()
{
    scanf("%d", &n);
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= n; j++)
            scanf("%d", &ins[i][j]);

    func(0);
    int min = 9999;
    for (int i = 0; i < cnt; i++)
    {
        int start = path[i][0];
        int instance = 0;
        for (int j = 0; j < n - 1; j++)
            instance += ins[path[i][j]][path[i][j + 1]];
        instance += ins[path[i][n - 1]][start];
        if (min > instance)
            min = instance;
    }
    printf("%d\n", min);

    return 0;
}

7-3 最短路径

给定一个有N个顶点和E条边的无向图,顶点从0到N−1编号。请判断给定的两个顶点之间是否有路径存在。如果存在,给出最短路径长度。
这里定义顶点到自身的最短路径长度为0。
进行搜索时,假设我们总是从编号最小的顶点出发,按编号递增的顺序访问邻接点。

输入格式:

输入第1行给出2个整数N(0<N≤10)和E,分别是图的顶点数和边数。
随后E行,每行给出一条边的两个顶点。每行中的数字之间用1空格分隔。
最后一行给出两个顶点编号i,j(0≤i,j<N),i和j之间用空格分隔。

输出格式:

如果i和j之间存在路径,则输出"The length of the shortest path between i and j is X.“,X为最短路径长度,
否则输出"There is no path between i and j.”。

输入样例1:

7 6
0 1
2 3
1 4
0 2
1 3
5 6
0 3

输出样例1:

The length of the shortest path between 0 and 3 is 2.

输入样例2:

7 6
0 1
2 3
1 4
0 2
1 3
5 6
0 6

输出样例2:

There is no path between 0 and 6.

代码

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int n;
int e;
int a;
int b;
int len;
int min = 9999;
int flag[100];
int path[100] = {0};
int v[100][100];

void func(int start, int cnt)
{
    int index = 0;
    for (int i = 0; i < n; i++)
        if (v[start][i] == 1 && flag[i] == 0)
        {
            len = cnt;
            path[index++] = i;
            flag[i] = 1;
            if (i == b)
                return;
        }
    while (index--)
        func(path[index], cnt + 1);
}

int main()
{
    scanf("%d %d", &n, &e);
    for (int i = 0; i < e; i++)
    {
        scanf("%d %d", &a, &b);
        v[a][b] = 1;
        v[b][a] = 1;
    }
    scanf("%d %d", &a, &b);
    if (a > b)
    {
        int temp = a;
        a = b;
        b = temp;
    }
    len = 0;
    flag[a] = 1;
    func(a, 1);
    if (flag[b] == 0)
        printf("There is no path between %d and %d.", a, b);
    else
        printf("The length of the shortest path between %d and %d is %d.", a, b, len);

    return 0;
}

9分支限界

判断

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1P8FUwb1-1672303453676)(算法pta.assets/image-20221228213402934-167223444447513.png)]

选择

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4EC32h8E-1672303453676)(算法pta.assets/image-20221228213426615-167223446804314.png)]

接下来的n行,每行有两个数据,分别表示第i(1≤i≤n)件物品的重量和价值。

输出格式:

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

输入样例:

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

5 10
2 6
2 3
6 5
5 4
4 6

输出样例:

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

15

代码

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int n;
int c;
int cnt;
int w[105];
int v[105];
int x[105];
int path[10000][1000];

void func(int level, int num)
{
    if (level == n)
    {
        x[n] = num;
        for (int i = 1; i <= n; i++)
            path[cnt][i] = x[i];
        cnt++;
    }
    else
    {
        if (level != 0)
            x[level] = num;
        func(level + 1, 0);
        func(level + 1, 1);
    }
}

int main()
{
    int max = -1;
    scanf("%d %d", &n, &c);
    for (int i = 1; i <= n; i++)
        scanf("%d %d", &w[i], &v[i]);
    for (int i = 1; i <= n; i++)
        for (int j = 0; j <= c; j++)
        {
            path[i][j] = path[i - 1][j];
            if (j >= w[i])
            {
                path[i][j] = path[i - 1][j] > path[i - 1][j - w[i]] + v[i] ? path[i - 1][j] : path[i - 1][j - w[i]] + v[i];
                if (path[i][j] > max)
                    max = path[i][j];
            }
        }
    printf("%d\n", max);
    return 0;
}

7-2 旅行售货员

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

输入格式:

第一行为城市数n

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

输出格式:

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

输入样例:

3
0 2 1
1 0 2
2 1 0

输出样例:

3

代码

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int n;
int c;
int cnt;
int x[105];
int flag[100];
int ins[100][100];
int path[10000][100];

void func(int level)
{
    if (level == n)
    {
        for (int i = 0; i < n; i++)
            path[cnt][i] = x[i];
        cnt++;
    }
    else
    {
        for (int i = 1; i <= n; i++)
        {
            if (flag[i] == 1)
                continue;
            flag[i] = 1;
            x[level] = i;
            func(level + 1);
            flag[i] = 0;
        }
    }
}

int main()
{
    scanf("%d", &n);
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= n; j++)
            scanf("%d", &ins[i][j]);

    func(0);
    int min = 9999;
    for (int i = 0; i < cnt; i++)
    {
        int start = path[i][0];
        int instance = 0;
        for (int j = 0; j < n - 1; j++)
            instance += ins[path[i][j]][path[i][j + 1]];
        instance += ins[path[i][n - 1]][start];
        if (min > instance)
            min = instance;
    }
    printf("%d\n", min);

    return 0;
}

7-3 最短路径

给定一个有N个顶点和E条边的无向图,顶点从0到N−1编号。请判断给定的两个顶点之间是否有路径存在。如果存在,给出最短路径长度。
这里定义顶点到自身的最短路径长度为0。
进行搜索时,假设我们总是从编号最小的顶点出发,按编号递增的顺序访问邻接点。

输入格式:

输入第1行给出2个整数N(0<N≤10)和E,分别是图的顶点数和边数。
随后E行,每行给出一条边的两个顶点。每行中的数字之间用1空格分隔。
最后一行给出两个顶点编号i,j(0≤i,j<N),i和j之间用空格分隔。

输出格式:

如果i和j之间存在路径,则输出"The length of the shortest path between i and j is X.“,X为最短路径长度,
否则输出"There is no path between i and j.”。

输入样例1:

7 6
0 1
2 3
1 4
0 2
1 3
5 6
0 3

输出样例1:

The length of the shortest path between 0 and 3 is 2.

输入样例2:

7 6
0 1
2 3
1 4
0 2
1 3
5 6
0 6

输出样例2:

There is no path between 0 and 6.

代码

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int n;
int e;
int a;
int b;
int len;
int min = 9999;
int flag[100];
int path[100] = {0};
int v[100][100];

void func(int start, int cnt)
{
    int index = 0;
    for (int i = 0; i < n; i++)
        if (v[start][i] == 1 && flag[i] == 0)
        {
            len = cnt;
            path[index++] = i;
            flag[i] = 1;
            if (i == b)
                return;
        }
    while (index--)
        func(path[index], cnt + 1);
}

int main()
{
    scanf("%d %d", &n, &e);
    for (int i = 0; i < e; i++)
    {
        scanf("%d %d", &a, &b);
        v[a][b] = 1;
        v[b][a] = 1;
    }
    scanf("%d %d", &a, &b);
    if (a > b)
    {
        int temp = a;
        a = b;
        b = temp;
    }
    len = 0;
    flag[a] = 1;
    func(a, 1);
    if (flag[b] == 0)
        printf("There is no path between %d and %d.", a, b);
    else
        printf("The length of the shortest path between %d and %d is %d.", a, b, len);

    return 0;
}

9分支限界

判断

[外链图片转存中…(img-1P8FUwb1-1672303453676)]

选择

[外链图片转存中…(img-4EC32h8E-1672303453676)]

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值