Poj 3624
一,简单回溯超时
问题分析
即深度优先策略枚举,通过约束函数和限界函数进行大幅度的剪枝,从而得到最优解,此处的递归式回溯(而且约束,限界函数都很简单),结果超时。
#include <stdio.h>
#include <stdlib.h>
#define NMAX 3450
int Listt[NMAX]; //存单次路径的状态
int WD[NMAX][3];//存W和D
int Best;//存最优结果
int N;
int M;
void Inp(void)//输入
{
scanf("%d %d",&N,&M);
for(int i=1;i<=N;i++)
{
Listt[i]=0;//可以不写
scanf("%d %d",&WD[i][1],&WD[i][2]);
}
Best=0;
}
void judge(void)//判断是否最优
{
int dnow=0;
for(int i=1;i<=N;i++)
{
dnow+=WD[i][2]*Listt[i];
}
if(dnow>Best)
{
Best=dnow;
}
}
int Ab(int t)//约束函数,条件为若加入当前物品超重
{
int wnow=0;
for(int i=1;i<=t;i++)
{
wnow+=WD[i][1]*Listt[i];
}
if(wnow>M)
{
return 0;
}
else
{
return 1;
}
}
int Ba(int t)//限界函数,条件为当前路径(不完整的)价值与剩余全部价值总和小于已知最优;
{
int dnow=0,dleft=0;
for(int i=1;i<=t;i++)
{
dnow+=WD[i][2]*Listt[i];
}
for(int j=t+1;j<=N;j++)
{
dleft+=WD[j][2];
}
if(dnow+dleft<Best)
{
return 0;
}
else
{
return 1;
}
}
void backtrack(int t)//递归回溯模版
{
if(t>N)//判断路径是否为目的路径
{
judge();
}
else
{
for(int i=0;i<=1;i++)//范围
{
Listt[t]=i;//更新结点
if(Ab(t)==1&&Ba(t)==1)//深入条件
{
backtrack(t+1);
}
}
}
}
int main()
{
Inp();
backtrack(1);
printf("%d\n",Best);
return 0;
}
尝试优化
显然这个还可以进行优化,比如把记录wnow,dnow,计算left的过程提出来减少重复计算,优化约束函数,限界函数等;比如先以W为权值排序方便剪枝;
二,动态规划
英语:Dynamic programming,简称DP
二维dp超内存
问题分析
尝试用dp解决此问题,划分依据为对第n个物品的放与不放,所以有
1. 子问题: 前n-1个物品占w重量的价值;
2. 状态:dp[n][m]表示n个备选物品提供m容量的背包,所得到的最大价值;
3. 边界:dp[0][m];
4. 状态转移方程:a). 没有对于n的空间剩余,直接就不放,此时其最大价值为dp[n-1][m] ;b). 有对于n的空间剩余,dp[n][m]=max( dp[n-1][m-Wn]+Dn , dp[n-1][m] );其中 dp[n-1][m-Wn]+Dn 表示m的容量中第n个物品占Wn容量,产生Dn价值,剩余m-Wn的容量产生的价值由前n-1个物品决定; dp[n-1][m] 表示第n个物品不放入,则此时背包总价值由m容量承载的n-1个物品决定;显然具有无后效性和最优子结构的特性;
实现
#include <stdio.h>
#define NMAX 1000
#define MMAX 1000
int dp[NMAX][MMAX];//dp表
int W[NMAX];
int D[NMAX];
int N;
int M;
void Inp(void)//输入
{
scanf("%d %d",&N,&M);
for(int i=1;i<=N;i++)
{
scanf("%d %d",&W[i],&D[i]);
}
}
void Init(void)初始化
{
for(int i=0;i<=M;i++)
{
dp[0][i]=0;
}
for(int j=1;j<=N;j++)
{
dp[j][0]=0;
}
}
void makeone(void)//得到结果
{
Init();
int F=0,T=0;
for(int i=1;i<=N;i++)
{
for(int j=1;j<=M;j++)
{
if(j-W[i]<0)//放不下时,可以利用比较实现对有没有空间放n的统一化;缺点是多了一次没用的比较(0和dp[n-1][m]);
{
T=0;
}
else
{
T=dp[i-1][j-W[i]]+D[i];
}
F=dp[i-1][j];
dp[i][j]=(T>F?T:F);//得到最大值
//printf("%d\t",dp[i][j]);
}
//printf("\n");
}
}
int main()
{
Inp();
makeone();
printf("%d",dp[N][M]);
return 0;
}
分析
二维的dp造成了大量的空间浪费,因为状态转移方程显示当前状态只与它前一行有关联,所以每一行的结果只需要前一行的数据就可以得到,又因为dp[n][m]所需数据的列都在它的前面,所以对每一行从后往前计算,就可以实现只用一个一维数组实现递推过程;
一维dp 成功AC
#include <stdio.h>
#define NMAX 3500
#define MMAX 13000
int dp[MMAX];
int W[NMAX];
int D[NMAX];
int N;
int M;
void Inp(void)
{
scanf("%d %d",&N,&M);
for(int i=1;i<=N;i++)
{
scanf("%d %d",&W[i],&D[i]);
}
}
void Init(void)
{
for(int i=0;i<=M;i++)
{
dp[i]=0;
}
}
void makeone(void)
{
Init();
int F=0,T=0;
for(int i=1;i<=N;i++)
{
for(int j=M;j>=1;j--)
{
if(j-W[i]<0)//放不下
{
T=0;
}
else
{
T=dp[j-W[i]]+D[i];
}
F=dp[j];
dp[j]=(T>F?T:F);
//printf("%d\t",dp[j]);
}
//printf("\n");
}
}
int main()
{
Inp();
makeone();
printf("%d",dp[M]);
return 0;
}
就题论题
从题目中给的N最大3402;M最大12880;虽然不知道数据怎么来的但明显暗示你如果用动态规划状态肯定是dp[n][m],并且暗示你二维的dp是要超内存的;所以自然就又一维dp的线索给你了;
思考与学习
- 解决dp问题,尽量在写完状态转移方程后,考虑以下降维的问题,以防止内存超限;
- 同时也给我们一个思路,写完程序后主动去尝试优化它的存储结构和提取公共部分到外部或是把简单的公共部分空间换时间;比如此题回溯实现的left数组完全可以先计算出来,存为表,即用即查;
- 递归式回溯可能不太适用于层数过大的问题;
- 递归式回溯的全局变量选择;比如本题回溯实现的wnow和dnow;
- 尝试用有序来优化一些回溯的约束函数和限界函数;