T1.P1064.[NOIP2006 提高组] 金明的预算方案![](https://i-blog.csdnimg.cn/blog_migrate/eec64af32230f674c24504d0ea92a199.png)
需要思考的地方:
1.可以转化成背包吗,是什么样的背包嘞
对于没有主件的附件可以直接当成01背包,对于有附件的主件在确保选择了主件之后也可以依据剩余容量选择是否要附件,这是只要在01背包的基础上加上if语句来判断买附件和不买附件的最优解
2.状态转移方程是什么
有了上述思路的话状态转移方程就是这样:
dp[j]=max(dp[j],dp[j-main_c[i]]+main_v[i]);
if(j>=main_c[i]+attach_c[i][1])//是否选第一个附件
dp[j]=max(dp[j],dp[j-main_c[i]-attach_c[i][1]]+main_v[i]+attach_v[i][1]);
if(j>=main_c[i]+attach_c[i][2])//是否选第二个附件
dp[j]=max(dp[j],dp[j-main_c[i]-attach_c[i][2]]+main_v[i]+attach_v[i][2]);
if(j>=main_c[i]+attach_c[i][2]+attach_c[i][1])//是否都选
dp[j]=max(dp[j],dp[j-main_c[i]-attach_c[i][1]-attach_c[i][2]]+main_v[i]+attach_v[i][1]+attach_v[i][2]);
3.对于有附件的主件的选取顺序是什么
先保证选主件,在判断选不选附件以及选几个
思路:
1.初始化数据(附件开二维数组因为留下一维用来表示是第几个附件)
2.遍历数组,按照前面讲的来转移状态即可
代码如下:
#include<bits/stdc++.h>
using namespace std;
const int N=32005;
int main_v[65],main_c[65];//主件
int attach_v[65][3],attach_c[65][3];//附件,_v->value;_c->cost
int dp[N];
int n,m;
int main()
{
cin>>n>>m;
for(int i=1;i<=m;i++)
{
int a,b,c;
cin>>a>>b>>c;
if(c!=0)
{
attach_c[c][0]++;//第二维第0个数用来判断是第几个附件
attach_c[c][attach_c[c][0]]=a;
attach_v[c][attach_c[c][0]]=a*b;//初始化这个附件的参数(这个方法不错)
}
else
{
main_c[i]=a;//主件
main_v[i]=a*b;
}
}
for(int i=1;i<=m;i++)
{
for(int j=n;main_c[i]!=0&&j>=main_c[i];j--)//main_c[i]!=0保证一定先选主件再考虑选附件
{ //从大到小遍历
dp[j]=max(dp[j],dp[j-main_c[i]]+main_v[i]);
if(j>=main_c[i]+attach_c[i][1])//是否选第一个附件
dp[j]=max(dp[j],dp[j-main_c[i]-attach_c[i][1]]+main_v[i]+attach_v[i][1]);
if(j>=main_c[i]+attach_c[i][2])//是否选第二个附件
dp[j]=max(dp[j],dp[j-main_c[i]-attach_c[i][2]]+main_v[i]+attach_v[i][2]);
if(j>=main_c[i]+attach_c[i][2]+attach_c[i][1])//是否都选
dp[j]=max(dp[j],dp[j-main_c[i]-attach_c[i][1]-attach_c[i][2]]+main_v[i]+attach_v[i][1]+attach_v[i][2]);
}
}
cout<<dp[n];
}
T2.P1077 [NOIP2012 普及组] 摆花
需要思考的问题:
1.状态转移方程是啥:
这题设dp[i][j]为前i种取j朵的最大值,则显然有dp[i][j]=dp[i-1][j]+dp[i-1][j-1]+....dp[i-1][j-amt[i]]
2.这样的状态转移怎么转化成代码:
就是完全背包里多了一层for循环
for(int i=1;i<=n;i++)
{
for(int j=0;j<=m;j++)
{
for(int k=0;k<=min(j,amt[i]);k++)//多了一次循环
dp[i][j]=(dp[i][j]+dp[i-1][j-k])%mod;//每一次都除mod就行
}
}
然后其他好像就没什么了,剩下的就是代码的一些细节比如dp[0][0]=1
思路:
1.初始化数据
2.三层循环,搞定~🤣
代码如下:
#include<bits/stdc++.h>
using namespace std;
const int mod=1e6+7;
int n,m;
int amt[105];
long long dp[105][105];//dp[i][j]前i种取j朵
int main() //dp[i][j]=dp[i-1][j]+dp[i-1][j-1]+....dp[i-1][j-amt[i]]
{ //dp[0][0]=1(啥都不取也算一种)
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>amt[i];
dp[0][0]=1;
for(int i=1;i<=n;i++)
{
for(int j=0;j<=m;j++)
{
for(int k=0;k<=min(j,amt[i]);k++)//多了一次循环
dp[i][j]=(dp[i][j]+dp[i-1][j-k])%mod;//每一次都除mod就行
}
}
cout<<dp[n][m];
}
这题还能用滚动数组优化,代码如下:
include<bits/stdc++.h>
using namespace std;
const int maxn=105, mod = 1000007;
int n, m, a[maxn], f[2][maxn], t;
int main()
{
cin>>n>>m;
for(int i=1; i<=n; i++) cin>>a[i];
f[0][0] = 1;
for(int i=1; i<=n; i++)
{
t = 1-t; //滚动
for(int j=0; j<=m; j++)
{
f[t][j] = 0; //注意初始化
for(int k=0; k<=min(j, a[i]); k++)
f[t][j] = (f[t][j] + f[1-t][j-k])%mod;
}
}
cout<<f[t][m]<<endl;
return 0;
emmm看到数据比较小其实也可以用dfs做啦(注意要加上记忆化搜索)
#include<bits/stdc++.h>
using namespace std;
const int maxn=105, mod = 1000007;
int n, m, a[maxn], rmb[maxn][maxn];
int dfs(int x,int k)
{
if(k > m) return 0;
if(k == m) return 1;
if(x == n+1) return 0;
if(rmb[x][k]) return rmb[x][k]; //搜过了就返回
int ans = 0;
for(int i=0; i<=a[x]; i++) ans = (ans + dfs(x+1, k+i))%mod;
rmb[x][k] = ans; //记录当前状态的结果
return ans;
}
int main()
{
cin>>n>>m;
for(int i=1; i<=n; i++) cin>>a[i];
cout<<dfs(1,0)<<endl;
return 0;
}
T3.P1833 樱花
需要思考的问题:
1.这个时间怎么读取
scanf("%d:%d%d:%d",&start_h,&start_m,&end_h,&end_m);//scanf的好处不就来了
2.这又是什么类型的背包
感觉是完全背包和多重背包混起来(01背包也算特殊的多重背包...吧)
多重背包的话就涉及到二进制拆分了。
3.二进制拆分怎么实现
直接上代码-->
void binary_split()//进行二进制拆分
{
for(int i=1;i<=n;i++)
{
int t=1;
while(chance[i]!=0)//只要次数不为0就一直拆分下去
{
tp++;//tp表示这个大背包拆分成的第几个子背包
co[tp]=t*cost[i];//这个子背包的cost->个数*单个物品cost
v[tp]=t*value[i];//这个子背包的value->同上
chance[i]-=t;//表示用掉了t次选择机会
t*=2;//从前往后拆出来的背包容量差两倍
if(chance[i]<t)//如果剩下的不能再拆就直接放一起
{
tp++;//开一个子背包给剩余的物品
co[tp]=cost[i]*chance[i];//剩余物品总cost
v[tp]=value[i]*chance[i];//...总value
break;//---别漏
}
}
}
}
思路:
1.读取时间,算出总共能赏花的时间
2.读入数据,判断是能赏有限还是无数次,如果无数次的话就把次数赋充分大(但是不能太大)
3.然后二进制拆分,当成01背包来做就ok
代码如下:
#include<bits/stdc++.h>//二进制拆分->01背包
const int N=1e6+5;
int total_time,n,start_h,start_m,end_h,end_m;
int cost[10005],value[10005],chance[10005];//cost->花费时间 chance->观看次数 value->价值
int dp[1005];
int tp,co[N],v[N];//冷知识,这里tp不用赋初值就默认是0(但不知道为啥)
using namespace std;
void binary_split()//进行二进制拆分
{
for(int i=1;i<=n;i++)
{
int t=1;
while(chance[i]!=0)//只要次数不为0就一直拆分下去
{
tp++;//tp表示这个大背包拆分成的第几个子背包
co[tp]=t*cost[i];//这个子背包的cost->个数*单个物品cost
v[tp]=t*value[i];//这个子背包的value->同上
chance[i]-=t;//表示用掉了t次选择机会
t*=2;//从前往后拆出来的背包容量差两倍
if(chance[i]<t)//如果剩下的不能再拆就直接放一起
{
tp++;//开一个子背包给剩余的物品
co[tp]=cost[i]*chance[i];//剩余物品总cost
v[tp]=value[i]*chance[i];//...总value
break;//---别漏
}
}
}
}
int main()
{
scanf("%d:%d%d:%d",&start_h,&start_m,&end_h,&end_m);
/*也可以这样输入时间
for(int i = 1 ; i <= 2 ; i ++)//输两遍
{
cin >> a >> ch >> b ;//输入
pre[t] = a * 60 + b ;//时间
t ++ ;
}
*/
cin>>n;
total_time=(end_h*60+end_m)-(start_h*60+start_m);//总共有的时间
for(int i=1;i<=n;i++) //ps.第一次做把开始的-结束的...找了半天没发现哪错了
{
cin>>cost[i]>>value[i]>>chance[i];
if(chance[i]==0)
chance[i]=999999;//不要太大,一般百万就足够了(开INT_MAX会炸)
}
binary_split();
for(int i=1;i<=tp;i++)//考虑每个拆出来的物品
{
for(int j=total_time;j>=co[i];j--)//01背包板子
dp[j]=max(dp[j],dp[j-co[i]]+v[i]);
}
cout<<dp[total_time];
}
T4.P8707 [蓝桥杯 2020 省 AB1] 走方格
需要思考的地方:
1.第一眼看起来好像可以用dfs,但TLE了φ(* ̄0 ̄)
2.用dp的话,状态转移方程也不难找:由于只能向下或向右走,故有dp[i][j]=dp[i-1][j]+dp[i][j-1]
思路:
1.初始化
2.两层循环丢给他自己去转移就行
代码如下:
#include<bits/stdc++.h>
using namespace std;
int dp[35][35];//dp[i][j]表示走到i,j的路径数
int n,m; //由于只能向下或向右走,故有dp[i][j]=dp[i-1][j]+dp[i][j-1]
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)dp[i][1]=1;
for(int i=1;i<=m;i++)dp[1][i]=1;//把初始条件初始化
for(int i=2;i<=n;i++)
{
for(int j=2;j<=m;j++)
{
if(i%2==0&&j%2==0)dp[i][j]=0;
else dp[i][j]=dp[i-1][j]+dp[i][j-1];//转移
}
}
cout<<dp[n][m];
}