0-1背包 (50分)
给定n(n<=100)种物品和一个背包。物品i的重量是wi,价值为vi,背包的容量为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;
int n,c; //分别定义物品种类数n,背包容量为c
int rest; //rest用于存储当前背包剩余物品的总价值
int weight[150]; //定义weight数组用来存放n个物品的重量
int value[150]; //定义value数组用来存放n个物品的价值
int temResult[150]; //定义temResult数组,用来存放当前的选择情况,temResult[i]的值为1表示物品i被选,为0表示未选
int result[150]; //定义一个result数组,用来存放最佳选择情况
int totalWeight=0; //在当前选择的情况下物品的总重量
int temMaxNum=0,max_num=0; //temMaxNum表示当前选择情况下的价值 ,max_num表示最佳选择情况的价值
void Backtrack(int depth){
if(depth>n){ //搜索到了叶节点,判断一下当前选择情况下的价格tenMaxNum和max_num的大小,如果tenMaxNum大于max_num则更新最优值
if(temMaxNum > max_num){
for(int i =1;i<=n;i++)
result[i]=temResult[i];
max_num=temMaxNum;
}
return;
}
if(totalWeight+weight[depth] <= c){ // totalWeight+weight[depth]小于等于c,表示该物品能放入
temResult[depth]=1; //该物品放入背包,置1
rest-=value[depth]; //剩余物品价值要减掉这一层的物品
totalWeight+=weight[depth]; //总重量要加上这一层的物品
temMaxNum+=value[depth]; //总价值要加上这一层的物品
Backtrack(depth+1); //继续向下查找
rest+=value[depth]; //回溯,剩余价值rest加回这一层的value
temMaxNum-=value[depth]; //总价值要减去这一层的value
totalWeight-=weight[depth]; //总重量要减去这一层的weight
}
temResult[depth]=0;
if(rest+temMaxNum > max_num)Backtrack(depth+1); //剪枝,只有当当前总价值加上剩余的价值大于最优值,才有可能出现更好地最优值,这时候才继续向下查找
}
int main(){
cin>>n>>c;
for(int i=1;i<=n;i++){
cin>>weight[i]>>value[i];
rest+=value[i];
}
Backtrack(1);
cout<<max_num<<endl;
}
工作分配问题 (50分)
设有n件工作分配给n个人。将工作i分配给第j个人所需的费用为cij 。 设计一个算法,对于给定的工作费用,为每一个人都分配1 件不同的工作,并使总费用达到最小。
输入格式:
输入数据的第一行有1 个正整数n (1≤n≤20)。接下来的n行,每行n个数,表示工作费用。
输出格式:
将计算出的最小总费用输出到屏幕。
输入样例:
在这里给出一组输入。例如:
3
10 2 3
2 3 4
3 4 5
输出样例:
在这里给出相应的输出。例如:
9
方法1:排列树法
#include<iostream>
using namespace std;
int n; //n表示n件工作分配给n个人
int bestx=100000,fee=0; //bestx用于存储最优情况下的最小费用,fee表示当前选择情况下的总费用
int c[30][30]; //二维数组c用于存放所有费用
int x[30]; //数组x用于存放当前选择的情况
int result[30]; //用于存放最优选择
void Backtrace(int t){
if(t>n){ //搜索到叶子结点,判断是否要更新最优值
if(fee <bestx){ //如果所得费用比最优值的都小的话,就进行更新
bestx=fee;
for(int i=1;i<=n;i++)
result[i]=x[i];
}
}else{
for(int i=t;i<=n;i++){ //这是排列树 ,需要有x数组进行交换
fee += c[t][x[i]]; //t表示的是深度,也就是工作序号,x[i]表示分配给的人
swap(x[i],x[t]); //排列树中经典的交换
if(fee<bestx) Backtrace(t+1); //剪枝。只有当当前总费用小于最优值时再继续查找
swap(x[i],x[t]); //回溯,把之前交换的换回来
fee -= c[t][x[i]]; //回溯费用
}
}
}
int main(){
cin>>n;
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++)
cin>>c[i][j];
}
for(int i=1 ;i<=n;i++) //对x数组进行顺序初始化
x[i] = i;
Backtrace(1);
cout<<bestx<<endl;
// for(int i=1;i<=n;i++)
// cout<<result[i]<<" ";
return 0;
}
方法2:
#include<iostream>
using namespace std;
int n; //有n个任务分配给n个人
int c[30][30]; //定义二位数组c用来存放费用
int x[30]={0}; //x数组用来存放当前的分配情况,x[t]的值表示第t个任务分配给第x[t]个人
int result[30]; //存放最优选择情况
int minFee=100000,fee=0; //fee表示当前分配情况下的费用,minFee存放最优情况下的费用,即最小费用
bool place(int t){ //place用于判断第t个任务能不能分配给第x[t]个人
for(int i=1;i<t;i++) //从第一个开始遍历x数组
if(x[t]==x[i]) //如果x[t] == x[i]表示这个人已经有任务了,不能再分配
return false;
return true;
}
void Backtrack(int t){
if(t > n){ //到达叶子结点,判断一下是否要更新最优选择情况
if(fee < minFee){ //如果当前费用比最小费用还小,则更新最佳值
minFee = fee;
for(int i=1;i<=n;i++)
result[i]=x[i];
return;
}
}
for(int i=1;i<=n;i++){
x[t]=i; //把第t个任务分配给第i个人
fee+=c[t][i]; //总费用加上这个人所需的费用
if(fee < minFee && place(t)) //剪枝,只有当当前总费用比最小费用小,并且该任务可以被分配给第i个人(place函数用于判断),才继续向下查找
Backtrack(t+1);
fee-=c[t][i]; //回溯,更新总费用
}
}
int main(){
cin>>n;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
cin>>c[i][j];
Backtrack(1);
cout<<minFee<<endl;
// for(int i=1;i<=n;i++)
// cout<<result[i]<<" ";
//
}
自然数的拆分 (20分)
任何一个大于1的自然数n,总可以拆分成若干个小于n的自然数之和。 当n=7共14种拆分方法:
7=1+1+1+1+1+1+1
7=1+1+1+1+1+2
7=1+1+1+1+3
7=1+1+1+2+2
7=1+1+1+4
7=1+1+2+3
7=1+1+5
7=1+2+2+2
7=1+2+4
7=1+3+3
7=1+6
7=2+2+3
7=2+5
7=3+4
输入格式:
输入n, 1<n<20。
输出格式:
按字典序输出具体的方案。
输入样例:
在这里给出一组输入。例如:
7
输出样例:
7=1+1+1+1+1+1+1
7=1+1+1+1+1+2
7=1+1+1+1+3
7=1+1+1+2+2
7=1+1+1+4
7=1+1+2+3
7=1+1+5
7=1+2+2+2
7=1+2+4
7=1+3+3
7=1+6
7=2+2+3
7=2+5
7=3+4
#include<iostream>
using namespace std;
int n; //定义自然数n
int sum=0; //sum用于存储在当前选择下的拆分和
int a[10000]; //定义一个数组,用来存储拆分的数
void print(int t) //定义一个打印函数
{
cout<<n<<"=";
for(int i=1;i<=t-1;i++)
cout<<a[i]<<"+";
cout<<a[t]<<endl;
}
void dfs(int t)
{
for(int i=a[t-1];i<=sum;i++) //可能很多人会搞不懂为什么从a[t-1]开始,因为后拆出来的数字一定不小于前面拆出来的数字,所以从a[t-1]开始递增,保证每次拆出来的数一定不比前面小
{
if(i<n) //i小于n是因为我们在这里不需要把这个数拆成它本身,所以当i==n时,也就是i等于被拆分数的时候,我们不拆
{
a[t]=i; //i放入数组,表示再拆一个数字i出来
sum-=i; //数字i被拆出来,所以要减掉
if(sum==0) //如果sum==0,表示不用再拆了,刚好拆完,然后调用打印函数
print(t);
else
dfs(t+1); //如果sum!=0,则调用下一层,继续拆
sum+=i; //回溯,把在这一层拆掉的i加回去
}
}
}
int main()
{
cin>>n;
a[0]=1; //因为我们从a[1]开始保存拆分数,而且后面拆分数不小于前面的,所以我们把a[0]设置为1,就能保证所有拆分数都不小于1
sum = n; //初始化sum
dfs(1);
return 0;
}