作业比赛编号 : 1370 - 2023年春季学期《算法分析与设计》练习15

本文提供了六个编程问题,涉及递归求和、文件存储优化、图的着色、素数环构造和N皇后问题,均通过递归和动态规划等算法解决。每个问题都有详细的输入输出描述,以及解决方案的代码示例,展示了如何利用计算机科学的基本概念解决实际问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

问题 A: 简单递归求和

题目描述

使用递归编写一个程序求如下表达式前n项的计算结果:  (n<=100)
1 -  3 + 5 - 7 + 9 - 11 +......
输入n,输出表达式的计算结果。

输入

多组输入,每组输入一个n,n<=100。

输出

输出表达式的计算结果。

样例输入 Copy

1
2

样例输出 Copy

1
-2
#include<stdio.h>

int  fun(int n) {
	if (n == 0) return 0;
	else if (n == 1) return 1;
	else if (n % 2 == 0) return fun(n-1) - (2 * n - 1);
	else return fun(n-1)+(2 * n - 1);
}

int main() {
	int n;
	while (~scanf("%d", &n)) {

	
			
			int sum = fun(n);
			printf("%d\n", sum);
		
	}
	return 0;
}

问题 B: 递归求和

题目描述

使用递归编写一个程序求如下表达式的计算结果:  (1<n<=20)
S(n) = 1*4 + 4*9 + 9*16 + 16*25 + ... + ((n-1)^2)*n^2
输入n,输出表达式S(n)的结果。

输入

单组输入,输入一个正整数n,1<n<=20。

输出

输出表达式S(n)的计算结果。

样例输入 Copy

3

样例输出 Copy

40
#include<stdio.h>

int  fun(int n) {
	if (n == 2) return 4;
	else return fun(n - 1) + ((n - 1) * (n - 1) * n * n);
}

int main() {
	int n;
	while (~scanf("%d", &n)) {

		
			
			int sum = fun(n);
			printf("%d\n", sum);
			
	}
	return 0;
}

问题 C: 文件存储

题目描述

如果有n个文件{F1,F2,F3,…,Fn}需要存放在大小为M的U盘中,文件i的大小为Si,1<=i<=n。请设计一个算法来提供一个存储方案,使得U盘中存储的文件数量最多。

输入

多组输入,对于每组测试数据,每1行的第1个数字表示U盘的容量M(以MB为单位,不超过256*1000MB),第2个数字表示待存储的文件个数n。
第2行表示待存储的n个文件的大小(以MB为单位)。

输出

输出最多可以存放的文件个数。

样例输入 Copy

10000 5
2000 1000 5000 3000 4000

样例输出 Copy

4

这道题目是一个经典的背包问题,可以使用动态规划算法解决。动态规划算法主要是将问题划分成子问题并逐步求解,最后得出原问题的解。

首先,在主函数中读入U盘容量m和文件数量n,然后读入每个文件的大小,并将文件大小存储在一个数组s中。

接下来,使用dp数组记录每个状态的最优解,dp[j]表示当前容量为j的时候,可以存储的最多文件数量。从容量为m的状态开始循环,根据状态转移方程dp[j] = dp[j] > dp[j - s[i]] + 1 ? dp[j] : dp[j - s[i]] + 1,计算出下一个状态的最优解。

在循环结束后,返回dp[m],即为最多可以存放的文件个数。

由于背包问题的时间复杂度为O(nm),可以保证在U盘容量不超过256000MB时快速得到结果。

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

#define MAXN 1005

/**
 * 背包问题
 * 参数:
 * n:文件数量
 * m:U盘容量
 * s[]:文件大小数组
 * 返回值:最多可以存放的文件个数
 */
int knapsack(int n, int m, int s[]) {
    int dp[256 * 1000];    // dp数组用于记录每个状态的最优解
    memset(dp, 0, sizeof(dp));    // 初始化dp数组全为0
    int i, j;
    for (i = 1; i <= n; ++i) {
        for (j = m; j >= s[i]; --j) {
            // 状态转移方程
            dp[j] = dp[j] > dp[j - s[i]] + 1 ? dp[j] : dp[j - s[i]] + 1;
        }
    }
    return dp[m];    // 返回最优解
}

int main() {
    int m, n;
    int s[MAXN];
    while (~scanf("%d%d", &m, &n)) {
        for (int i = 1; i <= n; i++) {
            scanf("%d", &s[i]);
        }
        printf("%d\n", knapsack(n, m, s));
    }
    return 0;
}

问题 D: 图的m着色问题

题目描述

 给定无向连通图G和m种不同的颜色。用这些颜色为图G的各顶点着色,每个顶点着一种颜色。是否有一种着色法使G中每条边的2个顶点着不同颜色,请输出着色方案。

输入

输入第一行包含n,m,k分别代表n个结点,m条边,k种颜色,接下来m行每行有2个数u,v表示u和v之间有一条无向边,可能出现自环边,所以请忽略自环边。

输出

输出所有不同的着色方案,且按照字典序从小到大输出方案。

样例输入 Copy

3 3 3
1 2
1 3
2 3

样例输出 Copy

1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1

首先,在主函数中读入图的顶点数量n、边的数量m和颜色种类数量k,并且使用邻接矩阵adj数组记录图的边。然后,调用dfs函数从第一个顶点开始搜索可行的着色方案。

dfs函数的实现思路是:对于每一个顶点cur,从1~k枚举所有可能的颜色,检查与该顶点相邻的顶点的颜色是否相同,如果不同,则将cur顶点着上该颜色,然后递归进入下一个顶点。当所有顶点都着色完毕时,输出当前的着色方案,并将可行的着色方案数量count加1。如果当前顶点的所有颜色都不能满足要求,就需要返回到上一个状态,重新选择上一个顶点的颜色。

在dfs函数结束后,如果找到了可行的着色方案,则将其按照字典序从小到大输出。如果没有可行的着色方案,则输出"No solution."。

由于回溯算法的时间复杂度比较高,对于顶点数量较大的图,可能需要较长的时间才能得到结果。

#include<stdio.h>

#define MAXN 105
#define MAXM 10005

int n, m, k;
int adj[MAXN][MAXN];
int colors[MAXN];
int count;    // 记录可行的着色方案数量

void dfs(int cur) {
    if (cur > n) {
        for (int i = 1; i <= n; i++) {
            printf("%d ", colors[i]);
        }
        printf("\n");
        count++;
        return;
    }
    for (int i = 1; i <= k; i++) {
        int flag = 1;
        for (int j = 1; j < cur; j++) {
            if (adj[cur][j] == 1 && colors[j] == i) {
                flag = 0;    // 检查相邻顶点颜色是否相同
                break;
            }
        }
        if (flag) {
            colors[cur] = i;    // 着色
            dfs(cur + 1);
            colors[cur] = 0;    // 回溯
        }
    }
}

int main() {
    scanf("%d%d%d", &n, &m, &k);
    for (int i = 1; i <= n; i++) {
        colors[i] = 0;
        for (int j = 1; j <= n; j++) {
            adj[i][j] = 0;
        }
    }
    for (int i = 1; i <= m; i++) {
        int u, v;
        scanf("%d%d", &u, &v);
        adj[u][v] = 1;
        adj[v][u] = 1;
    }
    dfs(1);
    if (count == 0) {
        printf("No solution.\n");
    }
    return 0;
}

问题 E: 素数环

题目描述

现有1,2,3...,n,要求用这些数组成一个环,使得相邻的两个整数之和均为素数,要求你求出这些可能的环。

输入

输入正整数n。

输出

输出时从整数1开始逆时针输出,同一个环只输出一次,且满足条件的环应按照字典序从小到大输出。
注:每一个环都从1开始。

样例输入 Copy

6

样例输出 Copy

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

这道题可以用回溯的方法求解,步骤如下:

  1. 从1开始尝试构造环,逐步向后搜索;
  2. 当只剩下一个数时,检查第一个数与最后一个数之和是否为素数,如果是则表示已经找到了一个环;
  3. 尝试从剩余的数中选择下一个数,并检查当前数与前一个数之和是否为素数,如果不是则放弃这个选择继续尝试新的选择;
  4. 如果所有选择都不能满足条件,则回溯到上一层继续进行尝试;
  5. 将环的情况保存在一个数组中,防止重复输出,使用字典序排序。

    思路解析:
    回溯函数 backtrack 从1开始尝试构造环,使用一个visited数组来记录哪些数已经被选中。如果只剩下一个数,则检查第一个数和最后一个数之和是否为素数,如果是则表示找到了一个环,执行 print_solution 函数输出环的情况。

    如果没有构造完整个环,则尝试从剩余的数中选择下一个数,并检查当前数是否能够与前一个数形成素数。如果当前数不能与前一个数形成素数,则放弃这个选择,继续尝试其他的选择;如果可以形成素数,则将当前数添加到解x中,并将visited数组对应的值设为1,递归到下一层继续进行尝试。如果所有的选择都不能满足条件,则回溯到上一层继续进行尝试。

    由于同一个环只需要输出一次,需要使用一个

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

static int n, count;
static int* x, * visited;    // x数组存放回溯过程中已经选择的数,visited数组存放某个数是否已经被选择

/**
 * 判断一个数是否为素数
 */
int is_prime(int n) {
    int i;
    if (n < 2) {
        return 0;
    }
    for (i = 2; i <= n / 2; ++i) {
        if (n % i == 0) {
            return 0;
        }
    }
    return 1;
}

/**
 * 输出一组环
 */
void print_solution() {
    int i;
    for (i = 0; i < n; ++i) {
        printf("%d ", x[i]);
    }
    printf("\n");
    ++count;
}

/**
 * 回溯函数,尝试构造环
 */
void backtrack(int k) {
    int i;
    if (k == n) {
        if (is_prime(x[0] + x[n - 1])) {
            print_solution();    // 找到一个环
        }
    }
    else {
        for (i = 2; i <= n; ++i) {
            if (!visited[i]) {
                // 检查当前数与前一个数是否为素数
                if (k == 0 || is_prime(i + x[k - 1])) {
                    x[k] = i;
                    visited[i] = 1;
                    backtrack(k + 1);    // 递归到下一层
                    visited[i] = 0;
                }
            }
        }
    }
}

/**
 * 按字典序比较两个环的大小
 */
int cmp(const void* a, const void* b) {
    int i;
    int* pa = (int*)a, * pb = (int*)b;
    for (i = 0; i < n; ++i) {
        if (pa[i] != pb[i]) {
            return pa[i] - pb[i];
        }
    }
    return 0;
}

int main() {
    int i;
    scanf("%d", &n);
    x = (int*)malloc(n * sizeof(int));
    visited = (int*)calloc(n + 1, sizeof(int));
    x[0] = 1;    // 第一个数是1
    visited[1] = 1;    // 已选中1
    backtrack(1);
   
    free(x);
    free(visited);
    return 0;
}

问题 F: N皇后问题

题目描述

使用回溯法求解N后问题。

 

输入

皇后的个数。

输出

每一种方案及总方案数。

样例输入 Copy

4

样例输出 Copy

0 1 0 0
0 0 0 2
3 0 0 0
0 0 4 0
----------------
0 0 1 0
2 0 0 0
0 0 0 3
0 4 0 0
----------------
总方案数为:2

使用回溯法求解N后问题的步骤如下:

  1. 从第一列开始,依次放置每个皇后;
  2. 如果当前列放置了皇后,就不能再在该列放置皇后;
  3. 如果当前行放置皇后后,与之前放置的皇后产生了冲突(在同一行或同一对角线上),则撤回该行的皇后;
  4. 如果已经放置了n个皇后,则表示找到了一组解;
  5. 递归到下一行,继续放置皇后。

思路解析:
该代码中的 check 函数用于检查当前放置的皇后是否与之前放置的皇后产生冲突。如果在同一行或同一对角线上,就会产生冲突,返回 0;否则,返回 1

在 backtrack 函数中,尝试放置第k个皇后,如果不产生冲突,就递归到下一行继续放置皇后。如果已经放置了n个皇后,就表示找到了一组解,输出后计数器 count 加 1。如果所有的情况都尝试完毕,还没有找到合法的解,则函数返回上一级,进行回溯。

 

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

static int n;    // 皇后的个数
static int count;    // 方案数
static int* x;    // 存放解

/**
 * 检查x[k]是否与前面的皇后产生冲突
 */
int check(int k) {
    int i;
    for (i = 0; i < k; ++i) {
        if (x[i] == x[k] || abs(x[i] - x[k]) == abs(i - k)) {
            return 0;    // 产生冲突,返回0
        }
    }
    return 1;    // 未产生冲突,返回1
}

/**
 * 输出一组解
 */
void print_solution() {
    int i, j;
    for (i = 0; i < n; ++i) {
        for (j = 0; j < n; ++j) {
            if (x[i] == j) {
                printf("%d ", i + 1);
            }
            else {
                printf("0 ");
            }
        }
        printf("\n");
    }
    printf("----------------\n");
    ++count;
}

/**
 * 回溯函数,尝试放置第k个皇后
 */
void backtrack(int k) {
    int i;
    if (k == n) {
        print_solution();    // 找到一组解
    }
    else {
        for (i = 0; i < n; ++i) {    // 尝试放置第k个皇后
            x[k] = i;
            if (check(k)) {    // 如果不冲突,继续递归
                backtrack(k + 1);
            }
        }
    }
}

int main() {
    int i;
    scanf("%d", &n);
    x = (int*)malloc(n * sizeof(int));
    for (i = 0; i < n; ++i) {
        x[i] = -1;    // 初始化为-1
    }
    backtrack(0);    // 从第0行开始放置皇后
    printf("总方案数为:%d\n", count);
    free(x);    // 释放内存
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值