以下是前段时间做的算法课设,其实就是4道题目(对动态规划算法、分治策略、回溯法、贪心算法的简单应用)这几天忙考试,今天有空,正好把代码和题目都弄上来。
打算以后也好好记录学习和生活。
问题1:大整数乘法
问题描述
当你遇到两个100位的数相乘时,你该怎么办呢?此题求两个不超过150位的非负整数的积。
输出一行,即相乘后的结果。结果里不能有多余的前导0,即如果结果是342,那么就不能输出为0342。
输入样例
12345678900
98765432100
输出样例
1219326311126352690000
2设计思想
首先将两个大整数a(n位)、b(m位)分解为两个部分:ah,al,bh,bl。
其中ah代表大整数a的高位,al代表大整数的低位,a=ah*10^n/2+al,ah和al都为n/2位。 同理bh代表大整数b的高位,bl代表大整数的低位,b=bh*10^m/2+bl,bh和bl都为m/2位。
a=ah*10^n/2+al
b=bh*10^m/2+bl
两个大整数相乘转换成4个乘法运算ah*bh,ah*bl,al*bh,al*bl,继续分解直到有一个乘数为一位时停止,执行乘法运算并记录结果。将计算出的结果相加回溯,得出最终结果。
代码如下:
#include<stdlib.h>
#include<cstring>
#include <iostream>
using namespace std;
#define M 100
char sa[1000];
char sb[1000];
typedef struct _Node {
int s[M];//把输入的整数倒序存进去
int l; //长度
int c; //幂
} Node, *pNode;
//将一个n位的数分成两个n/2的数并存储,记录它的长度和次幂
void up(pNode src, pNode des, int st, int l) {
int i, j;
for (i = st, j = 0; i < st + l; i++, j++) {
des->s[j] = src->s[i];
}
des->l = l;
des->c = st + src->c;
}
void add(pNode pa, pNode pb, pNode ans) {
int i, cc, k, palen, pblen, len;
int ta, tb;
pNode temp;
//保证pa是高次幂
if ((pa->c < pb->c)) {
temp = pa;
pa = pb;
pb = temp;
}
ans->c = pb->c; //结果的幂取最少的幂
cc = 0;
palen = pa->l + pa->c; //pa的长度
pblen = pb->l + pb->c; //pb的长度
//选取最长的长度
if (palen > pblen)
len = palen;
else
len = pblen;
k = pa->c - pb->c;
//k是幂差,len是最长的位数
for (i = 0; i < len - ans->c; i++) {
if (i < k)
ta = 0;
else
ta = pa->s[i - k];
if (i < pb->l)
tb = pb->s[i];
else
tb = 0;
if (i >= pa->l + k)
ta = 0;
ans->s[i] = (ta + tb + cc) % 10;
cc = (ta + tb + cc) / 10;
}
if (cc)
ans->s[i++] = cc;
ans->l = i;
}
//不断地分解,直到有一个乘数为1位数时停止分解
//进行乘法并记录结果
void mul(pNode pa, pNode pb, pNode ans) {
int i, cc, w;
int ma = pa->l >> 1, mb = pb->l >> 1;//做右移运算 四位数变两位数 两位数变一位数
Node ah, al, bh, bl;
Node t1, t2, t3, t4, z;
pNode temp;
if (!ma || !mb) {
//如果pa是一位数,则和pb交换
if (!ma) {
temp = pa;
pa = pb;
pb = temp;
}
ans->c = pa->c + pb->c;
w = pb->s[0]; //pb必为一位数
cc = 0;
for (i = 0; i < pa->l; i++) {
//pa必为2位数以上
ans->s[i] = (w * pa->s[i] + cc) % 10;
cc = (w * pa->s[i] + cc) / 10;
}
if (cc)
ans->s[i++] = cc;
ans->l = i;
return;
}
up(pa, &ah, ma, pa->l - ma); //高位升幂
up(pa, &al, 0, ma); //低位幂不变
up(pb, &bh, mb, pb->l - mb);
up(pb, &bl, 0, mb);
mul(&ah, &bh, &t1);
mul(&ah, &bl, &t2);
mul(&al, &bh, &t3);
mul(&al, &bl, &t4);
//把结果全部相加
add(&t3, &t4, ans);
add(&t2, ans, &z);
add(&t1, &z, ans);
}
int main() {
Node ans, a, b;
cout << "输入大整数 A:" << endl;
cin >> sa;
cout << "输入大整数 B:" << endl;
cin >> sb;
a.l = strlen(sa);
b.l = strlen(sb);
//对存入的sa转存到a中
int z = 0, i;
for (i = a.l - 1; i >= 0; i--)
a.s[z++] = sa[i] - '0';//转成整数
a.c = 0;
z = 0;//角标置0
//对存入的sb转存到b中,同上,类似的代码
for (i = b.l - 1; i >= 0; i--)
b.s[z++] = sb[i] - '0';//转成整数
b.c = 0;
mul(&a, &b, &ans);
cout << "A * B = :";
for (i = ans.l - 1; i >= 0; i--)
cout << ans.s[i];
cout << endl;
return 0;
}
运行结果:
问题2:线性模型 (过桥问题)
1问题描述
在一个夜黑风高的晚上,有n(n<= 50)个小朋友在桥的这边,现在他们需要过桥,但是由于桥很窄,每次只允许不大于两人通过,他们只有一个手电筒,所以每次过桥的两个人需要把手电筒带回来,i号小朋友过桥的时间为T[i],两个人过桥的总时间为二者中时间长者。问所有小朋友过桥的总时间最短是多少。2设计思想
输入:
两行数据:第一行为小朋友个数n
第二行有n个数,用空格隔开,分别是每个小朋友过桥的时间。
输出:
一行数据:所有小朋友过桥花费的最少时间。
一开始想的是让那个最快的人充当“跑腿”的人传递手电筒,这是典型的贪心算法,并没有考虑到整体的最优解。最后发现结果不太对。
策略:每次都让走路速度相近的一起过桥,但每次送手电筒依旧肯定选最快的,就是把送手电筒和过桥分开考虑。
将N个小朋友中的一个小朋友看成“跑腿”的,那么它需要将N-1个小朋友都送到河对岸,鉴于每次都要回来,故是2(N-1)次,由于最后一次跑腿人也一起过来了,(此时河对岸已经没有人了,就不用再回去)所以就是2(N-1)-1 = 2N-3次
- 假设现在河两边各有一群人,手电筒在对岸,我们需要选择“跑腿”,选择速度最快的人,这一定是最优决策,因此让r[0]回来。
2.此时河这边有r[0],r[i]~r[n-1],让r[i]和r[i+1]两个速度相近的人一起过桥,之后再由对岸选择一个“跑腿”(即r[1],此时他最快),然后由r[1]回来,r[1]和r[0]再一同过桥,这样让两个实力均衡的人一起,不会亏太多。
状态转移方程dp[i] = min{dp[i-1] + r[0] + r[i] , dp[i-2] + r[0] + r[i] + 2*r[1] }
基本要求:河对岸有大于等于两个人。
初始化dp[1]=r[1]; dp[2]=r[2]; dp[3]=r[2]+r[1]+r[3];
若输入人数小于等于3,则直接输出。
#include <iostream>
#include <iostream>
#include <algorithm>
//4
//1 2 5 10
//17
using namespace std;
int dp[100];
int r[100];
int DP(int n)
{
if(n==1||n==2)
return r[0];
dp[0]=r[0];//1
dp[1]=r[1];//2
dp[2]=r[2]+r[0]+r[1];//8 让1和2过河,1把电筒送回来,1和5再过
for(int i=3;i<n;++i)
{
dp[i] = min(dp[i-1]+r[0]+r[i],dp[i-2]+r[i]+r[0]+2*r[1]);//n=4
} /*dp[3] = dp[2]+r[0]+r[3] ,dp[1]+r[3]+r[0]+2*r[1]
两种情况:
第一种: 1 2过河,1把电筒送回来,1和5再过,1再带回来,和10一起过 即2+1+5+1+10=19
第二种: 1 2过,1带回,5 10过,2带回,1 2再一起过 即2+1+10+2+2=17
*/
return dp[n-1];
}
int main()
{
int n;
cin>>n;
for(int i=0;i<n;i++)
{
cin>>r[i];
}
sort(dp,dp+n);
cout<<DP(n);
}
运行结果:
问题3:素数环
1问题描述
素数环问题
(1)问题描述:输入正整数n,把整数1,2,3,4…..n组成一个环,使得相邻的两个整数之和均为素数。
(2)样例
输入:
6
输出:
1 4 3 2 5 6
- 6 5 2 3 4
(3)提示:n=4时的搜索解空间树。
2设计思想
(1)针对所给的问题,定义问题的解空间。
(2)确定易于搜索的解空间结构(排列树、子集树)
(3)从根节点开始,采用深度优先的方式遍历解空间树。
采用剪枝策略提高搜索效率。
(1)用约束函数在扩展结点处剪去不满足约束条件的子树;
(2)用限界函数剪去得不到最优解的子树。
一开始没想到要怎么做,看了题目的提示后发现这道题本质上考察了搜索解空间树,理解逻辑之后并不难。需要注意的点是:用约束函数在扩展结点处剪去不满足约束条件的子树和用限界函数剪去得不到最优解的子树。
代码如下
#include<iostream>
#include<algorithm>
using namespace std;
int A[100], prime[100], vis[100], n;//isp是素数表,用于存放素数
/*
(1)用约束函数在扩展结点处剪去不满足约束条件的子树;
(2)用限界函数剪去得不到最优解的子树。
*/
int is_prime(int x)//素数判断的函数
{
for(int i = 2; i < x; i++){
if(x % i == 0) return 0;
}
return 1;
}
void dfs(int cur)//深度优先遍历树
{
if(cur == n && prime[A[0] + A[n-1]]){//测试环的头尾之和是否为素数
for(int i = 0; i < n; i++){
cout << A[i] << " ";
}
cout << endl;
}
else
{
for(int i = 2; i <= n; i++)
{
//prime[i+A[cur-1]]是剪枝函数,不满足条件则把该支点剪掉,不往该子树递归
if(!vis[i] && prime[i+A[cur-1]]){
A[cur] = i;
vis[i] = 1;//当前i的值已经放在了cur的位置,当其它层次的递归遍历到值i时,避免重复
dfs(cur+1);
vis[i] = 0;//置0复位
A[cur] = 0;
}
}
}
}
int main(){
cin >> n;
for(int i = 2; i <= n*2; i++){
prime[i] = is_prime(i);
}
A[0] = 1;
dfs(1);
}
运行结果:
题目名称:站台安排问题
1问题描述
站台安排问题:一个火车站有一个所有火车到达和离开的时间表,需要找出最小站台数,使得按照此时间表调度时,可以容纳所有的火车。已知火车时刻表如下所示,试编程实现,需要的最少站台有几个。
火车 | 到达时间 | 离开时间 | 火车 | 到达时间 | 离开时间 |
车次A | 0900 | 0930 | 车次C | 1030 | 1100 |
车次B | 0915 | 1300 | 车次D | 1045 | 1145 |
2设计思想
1.贪心选择性质:因为活动安排问题每次安排活动时只需要考虑之前安排过的活动,不需要关心以后要安排的活动。因此该问题满足贪心选择性质。
2.最优子结构与性质:第n个活动安排时求的最优解一定是在第n-1个活动安排时求的最优解的基础之上的。因此该问题满足最优子结构与性质。
步骤:
1.定义一个结构体(或者类)用来保存需要安排活动的开始时间和结束时间。
2.按照开始时间的先后顺序进行排序,开始时间早的排在前面。
3.定义一个整数数组(初始化为0,大小为火车个数),用于保存每个火车的离开时间
4.对于每一个活动遍历车次,直到找到一个火车离开时间小于此次火车到站的时间为止。
代码如下:
#include <iostream>
#include <algorithm>
using namespace std;
struct huoche
{
int s;//存开始时间
int f;//存结束时间
};
huoche t[10000+10];
int n;
bool cmp(huoche a, huoche b) {
return a.s < b.s;
}
int meeting() {
int sum=0;
int *a = new int[n + 1]; //该数组于保存每个火车离开时间
for (int i = 1; i <=n; i++) {
a[i] = 0;
}
/*
0900 0930 1030 1100
0915 1300 1045 1145
a[1]=0 t[1].s=0900 sum=1
t[2].s=1030 a[1]=1100
t[3].s=0915 a[1]=1100 不满足
a[2]=0 进入if判断 a[2]=1300 sum=2
t[4].s=1045 a[1]=1100 不满足
a[2]=1300 不满足
a[3]=0 进入if判断 a[3]=t[4].f=1145 sum=3
a[4]=0 进入if判断 但是不满足j >= sum + 1
最终sum=3
*/
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
if (a[j] <= t[i].s) {
a[j] = t[i].f;
if (j >= sum + 1) //记录已使用了的站台数
sum++;
break;
}
}
}
delete[]a;
return sum;
}
//思路:先找最早开始的,然后找开始在前一个结束之后的
int main() {
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> t[i].s >> t[i].f;
}
sort(t+1,t+n+1,cmp);//排序,按开始时间排,早开始的排在前面
cout << meeting() << endl;
return 0;
}
运行结果: