YbtOJ 「动态规划」第1章 背包问题

不是很想开 dfs,来写点 dp 愉悦身心(bushi

例题1.采药问题

01 背包板子。写了滚动数组优化。

code
#include<bits/stdc++.h>
using namespace std;
const int N=105; 
int t,m,w[N],c[N],f[N*10];
int main()
{
	scanf("%d%d",&t,&m);
	for(int i=1;i<=m;i++) scanf("%d%d",&w[i],&c[i]);
	for(int i=1;i<=m;i++)
		for(int j=t;j;j--)
			if(j-w[i]>=0) f[j]=max(f[j],f[j-w[i]]+c[i]);
	cout<<f[t]<<endl;
	return 0;
}

例题2.货币系统

关键性质:B 集合属于 A,且 A 中能被集合内其他数构成的数不在 B 中。问题转化为判断 A 中哪些数能被其他数构成,这就是个完全背包板子了。

code
#include<bits/stdc++.h>
#define G() cr=getchar()

using namespace std;
int p,n;
int main()
{
        scanf("%d",&p);
	while(p--)
	{
		int ans=0;
		scanf("%d",&n); 
		int a[101]={0},b[25001]={0};
		for(int i=1;i<=n;i++)
		{
			scanf("%d",&a[i]);
			b[a[i]]=1;
		}
		sort(a+1,a+n+1);
		for(int i=1;i<=25000;i++)
		{
			if(!b[i]) continue;
			for(int j=1;j<=n;j++)
			{
				if(i+a[j]<=a[n]) b[i+a[j]]=2;
			}
		}
		for(int i=1;i<=25000;i++) if(b[i]==1) ans++;
		cout<<ans<<endl;
	}
	return 0;
}

例题3.宝物筛选

二进制拆分优化多重背包。依然是板子。(可能是个奇奇怪怪的二进制拆分写法/kel

code
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
int n,t,c[N],w[N];
int f[N],cnt;
int main()
{
	scanf("%d%d",&n,&t);
	for(int i=1,W,C,m;i<=n;i++)
	{
		scanf("%d%d%d",&C,&W,&m);
		int j;
		for(j=0;(1<<(j+1))-1<=m;j++)
		{
			w[++cnt]=(1<<j)*W,c[cnt]=(1<<j)*C;
		}
		w[++cnt]=W*(m-((1<<j)-1)),c[cnt]=C*(m-((1<<j)-1));
	}
	//for(int i=1;i<=cnt;i++) cout<<w[i]<<" "<<c[i]<<endl;
	for(int i=1;i<=cnt;i++)
		for(int j=t;j;j--)
			if(j-w[i]>=0) f[j]=max(f[j],f[j-w[i]]+c[i]); 
	cout<<f[t]<<endl;
	return 0;
}

例题4.硬币方案

直接用上一道题代码魔改。把重量设为硬币面值,价值随便设个数,背包容量为 \(m\)
\(f\) 数组初始值设为负无穷,则 \(f_j\) 求出来的就是重量和正好为 \(j\) 时的价值。
最后统计数组里有多少个不是负无穷即可。

code
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
int n,t,c[N],w[N];
int f[N],cnt;
int aa[N],cc[N];
int main()
{
	while(1)
	{
		scanf("%d%d",&n,&t);
		if(n==0&&t==0) break;cnt=0;
		for(int i=1;i<=n;i++) scanf("%d",&aa[i]);
		for(int i=1;i<=n;i++) scanf("%d",&cc[i]); 
		for(int i=1;i<=n;i++)
		{
			//scanf("%d%d%d",&C,&W,&m);
			int j,W=aa[i],C=1,m=cc[i];
			for(j=0;(1<<(j+1))-1<=m;j++)
			{
				w[++cnt]=(1<<j)*W,c[cnt]=(1<<j)*C;
			}
			w[++cnt]=W*(m-((1<<j)-1)),c[cnt]=C*(m-((1<<j)-1));
		}
		for(int i=0;i<=t;i++) f[i]=-2e9;f[0]=0;
		//for(int i=1;i<=cnt;i++) cout<<w[i]<<" "<<c[i]<<endl;
		for(int i=1;i<=cnt;i++)
			for(int j=t;j;j--)
				if(j-w[i]>=0) f[j]=max(f[j],f[j-w[i]]+c[i]); 
		int ans=0;
		for(int i=1;i<=t;i++) if(f[i]>=0) ans++;
		cout<<ans<<endl;
	}
	return 0;
}

例题5.金明的预算方案

一个物品只有两个附件。dp 过程分类讨论:

  • 只选主件
  • 主件和附件1
  • 主件和附件2
  • 三个都选
    其余和 01 背包板子相同。
code
#include<bits/stdc++.h>
#define G() cr=getchar()
using namespace std;
int xr;char cr;
int w1[61],v1[61],v2[61][3],w2[61][3],f[32001],m,n,p,q,v;
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++)
	{
		scanf("%d%d%d",&v,&p,&q);
		if(q)
		{
			w2[q][0]++;
			w2[q][w2[q][0]]=v;
			v2[q][w2[q][0]]=p*v;
		}
		else w1[i]=v,v1[i]=v*p;
	}
	for(int i=1;i<=m;i++)
	{
		for(int j=n;j>=w1[i];j--)
		{
			f[j]=max(f[j],f[j-w1[i]]+v1[i]);
			if(w1[i]+w2[i][1]<=j) f[j]=max(f[j],f[j-w1[i]-w2[i][1]]+v1[i]+v2[i][1]);
			if(j>=w1[i]+w2[i][2]) f[j]=max(f[j],f[j-w1[i]-w2[i][2]]+v1[i]+v2[i][2]);
			if(j>=w1[i]+w2[i][1]+w2[i][2])  
        	f[j]=max(f[j],f[j-w1[i]-w2[i][1]-w2[i][2]]+v1[i]+v2[i][1]+v2[i][2]);
        }
    }
    cout<<f[n]<<endl;
	return 0;
}

1.求好感度

依然是多重背包板子。说实话没看出来和例题 3 有啥区别。

code
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
int n,t,c[N],w[N];
int f[N],cnt;
int main()
{
	scanf("%d%d",&n,&t);
	for(int i=1,W,C,m;i<=n;i++)
	{
		scanf("%d%d%d",&m,&C,&W);
		int j;
		for(j=0;(1<<(j+1))-1<=m;j++)
		{
			w[++cnt]=(1<<j)*W,c[cnt]=(1<<j)*C;
		}
		w[++cnt]=W*(m-((1<<j)-1)),c[cnt]=C*(m-((1<<j)-1));
	}
	//for(int i=1;i<=cnt;i++) cout<<w[i]<<" "<<c[i]<<endl;
	for(int i=1;i<=cnt;i++)
		for(int j=t;j;j--)
			if(j-w[i]>=0) f[j]=max(f[j],f[j-w[i]]+c[i]); 
	cout<<f[t]<<endl;
	return 0;
}

2.购买商品

其实不用像书上一样 dfs。不好写而且时间复杂度高。
先对 A 物品做 01 背包,并把数组初始值设为负无穷。这样,找到 f 数组中最大的 \(f_i\)\(i\) 就是取最大值时花的钱。
再用剩下的钱对 B 做完全背包就好了。
时间复杂度 \(O(T(n+m))\)

code
#include<bits/stdc++.h>
using namespace std;
const int N=1000005;
int t,m,wa[N],ca[N],wb[N],cb[N],n;
int f[N];
int main()
{
	scanf("%d%d",&t,&n);
	for(int i=1;i<=n;i++) scanf("%d%d",&wa[i],&ca[i]);
	scanf("%d",&m);
	for(int i=1;i<=m;i++) scanf("%d%d",&wb[i],&cb[i]);
	for(int i=1;i<=t;i++) f[i]=-2e9;
	for(int i=1;i<=n;i++)
		for(int j=t;j;j--)
			if(j>=wa[i]) f[j]=max(f[j],f[j-wa[i]]+ca[i]);
	int maxn=-1,mon=0;
	for(int i=0;i<=t;i++)
	{
		if(f[i]>maxn) maxn=f[i],mon=i;
	}
	t-=mon;
	for(int i=1;i<=t;i++) f[i]=0;
	for(int i=1;i<=m;i++)
		for(int j=1;j<=t;j++)
			if(j>=wb[i]) f[j]=max(f[j],f[j-wb[i]]+cb[i]);
	cout<<f[t]<<endl;
	return 0;
}

3.魔法开锁

每个箱子向它所装钥匙对应箱子连边建图。则这个图每个点的入度出度均为 \(1\),即由若干个互不相交的环构成。
当每个环都有点被选时,能打开所有箱子。设 \(f_{i,j}\) 表示前 \(i\) 个环,共选了 \(j\) 个点时的方案数。
转移见代码。(懒得打 LaTeX
注意每个环至少选一个点,\(k\) 枚举时不带等号。

code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=305;
int T,n,t;
int a[N],fa[N];
double f[N][N];
double c[N][N];
int find(int x)
{
	if(fa[x]==x) return x;
	else return fa[x]=find(fa[x]);
}
void init()
{
	c[0][0]=1;
	for(int i=1;i<=300;i++)
	{
		c[i][0]=1;
		for(int j=1;j<=i;j++) c[i][j]=c[i-1][j-1]+c[i-1][j];//cout<<i<<" "<<j<<" "<<c[i][j]<<endl;
	}
}
int tt[N],cnt[N],tot;
signed main()
{
	scanf("%lld",&T);
	init();
	//printf("%.0lf\n",c[300][99]);
	while(T--)
	{
		scanf("%lld%lld",&n,&t);
		for(int i=1;i<=n;i++) fa[i]=i;
		for(int i=1;i<=n;i++)
		{
			tt[i]=cnt[i]=tot=0;
			scanf("%d",&a[i]);
			//cout<<find(a[i])<<" "<<find(i)<<endl;
			fa[find(a[i])]=find(i);
		}
		//for(int i=1;i<=n;i++) cout<<find(i)<<" ";
		for(int i=1;i<=n;i++) tt[find(i)]++;
		for(int i=1;i<=n;i++) if(tt[i]) cnt[++tot]=tt[i];
		for(int i=0;i<=tot;i++) for(int j=0;j<=t;j++) f[i][j]=0;
		f[0][0]=1;
		for(int i=1;i<=tot;i++)
		 	for(int j=0;j<=t;j++)
		 		for(int k=0;k<j;k++)
		 		{
		 			f[i][j]+=f[i-1][k]*c[cnt[i]][j-k];
		 			//cout<<cnt[i]<<" "<<j-k<<endl;
		 			//cout<<i<<" "<<j<<" "<<k<<" "<<f[i][j]<<" "<<f[i-1][k]<<" "<<c[cnt[i]][j-k]<<endl;
		 		}
		double ans1=f[tot][t],ans2=c[n][t];
		printf("%.6lf\n",ans1/ans2);
	}
	return 0;
}
	

4.购买礼物

为什么网上搜不到这题题解啊,书上代码给得跟没给一样/kel
\(f[i][j][k][0/1]\) 表示前 \(i\) 个物品,第一张卡花了 \(j\) 元,第二张卡花了 \(k\) 元,当前没用/用了免费卡。
状态设计出来了转移应该不难想。但是这题转移比较复杂,挂了一定多读几遍代码确认一下是不是手残问题()
关于判无解:\(f\) 数组初始值负无穷,看最后答案是否大于 \(0\) 即可。
其它奇奇怪怪的判无解方式会挂,显然本喵把所有的坑都踩了一遍挂了 2h...

code
#include<bits/stdc++.h>
using namespace std;
const int N=505;
int a,b,n,sum;
int w[N],c[N],s[N],flag[N];
int f[305][505][55][2];
int main()
{
	scanf("%d%d%d",&a,&b,&n);
	for(int i=1;i<=n;i++) scanf("%d%d%d",&w[i],&c[i],&s[i]);
	for(int i=0;i<=n;i++)
		for(int j=0;j<=a;j++)
			for(int k=0;k<=b;k++) 
				f[i][j][k][0]=f[i][j][k][1]=-2e9;
	f[0][0][0][0]=f[0][0][0][1]=0;
	for(int i=1;i<=n;i++)
	{
		for(int j=0;j<=a;j++)
		{
			for(int k=0;k<=b;k++)
			{
				if(s[i]==0)
				{
					f[i][j][k][0]=f[i-1][j][k][0];//不买 
					f[i][j][k][1]=f[i-1][j][k][1];
				}
				if(j>=w[i]) f[i][j][k][0]=max(f[i][j][k][0],f[i-1][j-w[i]][k][0]+c[i]),flag[i]=1;//第一张卡 
				if(k>=w[i]) f[i][j][k][0]=max(f[i][j][k][0],f[i-1][j][k-w[i]][0]+c[i]),flag[i]=1;//第二张卡 
				//cout<<i<<" "<<j<<" "<<k<<" "<<f[i][j][k][0]<<endl;
				f[i][j][k][1]=max(f[i][j][k][1],f[i-1][j][k][0]+c[i]);//免费
				if(j>=w[i]) f[i][j][k][1]=max(f[i][j][k][1],f[i-1][j-w[i]][k][1]+c[i]),flag[i]=1;//第一张卡 
				if(k>=w[i]) f[i][j][k][1]=max(f[i][j][k][1],f[i-1][j][k-w[i]][1]+c[i]),flag[i]=1;//第二张卡 
			}
		}
	}
	int maxn=-2e9;
	for(int i=0;i<=a;i++)
		for(int j=0;j<=b;j++)
			maxn=max(maxn,f[n][i][j][0]),maxn=max(maxn,f[n][i][j][1]);
	if(maxn<0) cout<<"-1"<<endl;
	else cout<<maxn<<endl;
	return 0;
}

5.课题选择

第三题简化版。
\(f_{i,j}\) 表示前 \(i\) 个课题共选了 \(j\) 篇所需的最小时间。
转移方程为 \(f_{i,j}=\min \{f_{i-1,k}+A_i\times (j-k)^{B_i}\}\)

code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=205;
int n,m,a[N],b[N];
int f[N][N];
int qpow(int n,int k)
{
	int ans=1;
	while(k)
	{
		if(k&1) ans*=n;
		n*=n;k>>=1;
	}
	return ans;
}
signed main()
{	
	scanf("%lld%lld",&n,&m);
	for(int i=1;i<=m;i++) scanf("%lld%lld",&a[i],&b[i]);
	for(int i=0;i<=max(n,m);i++) for(int j=0;j<=max(n,m);j++) f[i][j]=2e9;
	f[0][0]=0;
	for(int i=1;i<=m;i++)
		for(int j=0;j<=n;j++)
			for(int k=0;k<=j;k++)
				f[i][j]=min(f[i][j],f[i-1][k]+a[i]*qpow(j-k,b[i]));
	cout<<f[m][n]<<endl;
	return 0;
}
posted @ 2022-08-15 13:37  樱雪喵  阅读(84)  评论(0编辑  收藏  举报