USACO动态规划之背包问题1

序言

dp太辣鸡所以要刷题!做完USACO里dp专题的所有题!【然而还有数位dp插头dp的都还不会qwq


题目+题解

一、Subset Sums, USACO 1998 Spring


解题思路

因为平分,所以以和的一半当总容量做01背包。答案除以2,因为会重复算一次

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define maxn 11000

long long f[maxn];
int main()
{
	//freopen("subset.in","r",stdin);
	//freopen("subset.out","w",stdout);
	int n,i,j,sum;
	scanf("%d",&n);
	sum=(n+1)*n/4;
	memset(f,0,sizeof(f));
	f[0]=1;
	for (i=1;i<=n;i++)
	 for (j=sum;j>=i;j--)
	  f[j]=f[j]+f[j-i];
	if (n*(n+1)%4!=0) printf("0\n");
	else printf("%I64d\n",f[sum]/2);
	return 0;
}

二、[bzoj1739][poj2392]Space Elevator, USACO 2005 Mar


解题思路

算是加点限制的多重背包?
f[i]表示能否搭到高度为i..
if (f[j-k*a[i].h]) f[j]=1;k枚举使用第i块的个数

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<iostream>
using namespace std;
#define maxn 410

struct node
{
	int h,a,c;
}a[maxn];int f[maxn*100];
int mymin(int x,int y){return (x<y)?x:y;}
bool cmp(node x,node y)
{
	if (x.a!=y.a) return x.a<y.a;
	return (x.h*x.c)<(y.h*y.c);
}
int main()
{
	//freopen("elevator.in","r",stdin);
	//freopen("elevator.out","w",stdout);
	int n,i,j,k;
	scanf("%d",&n);
	for (i=1;i<=n;i++)
	{
		scanf("%d%d%d",&a[i].h,&a[i].a,&a[i].c);
		a[i].c=mymin(a[i].c,a[i].a/a[i].h);
	}
	memset(f,0,sizeof(f));
	sort(a+1,a+1+n,cmp);f[0]=1;
	for (i=1;i<=n;i++)
	  for (j=a[i].a;j>=0;j--)
		for (k=a[i].c;k>=0;k--)
		 if (j>=k*a[i].h && f[j-k*a[i].h]) f[j]=1;
	// for (i=0;i<=a[n].a;i++)
	 // if (f[i]) printf("%d\n",i);
	for (j=a[n].a;j>=0;j--) if (f[j]) break;
	printf("%d\n",j);
	return 0;
}


三、[poj2184]Cow Exhibition,USACO 2003 Fall



解题思路

f[i]就表示当智商为i时的最大情商。
因为有负数,所以有个基准值(设为)xx
转移:if(f[j-s[i]+xx]!=inf)  f[j+xx]=max(f[j+xx],f[j-s[i]+xx]+q[i]);
这样的话,因为s[i]的正负性不确定,你并不能知道j-s[i]与j的大小关系(即方程的转移方向)
所以要对s[i]正负的不同做不同的处理[WA了很久= =还不造为什么。。因为根本没有考虑到orz]

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define maxn 1001000
#define inf -1e9

int f[maxn],s[110],q[110];
int mymax(int x,int y){return (x>y)?x:y;}
int main()
{
	//freopen("osmrtfun.in","r",stdin);
	//freopen("osmrtfun.out","w",stdout);
	int n,i,x,y,j,ln,lm,ans,xx;
	ln=lm=0;ans=xx=0;
	scanf("%d",&n);
	for (i=1;i<=n;i++)
	{
		scanf("%d%d",&x,&y);
		if (x<0 && y<0) continue;
		s[++ln]=x;q[ln]=y;
		if (s[ln]>=0) lm+=s[ln];
		else xx+=-s[ln];
	}n=ln;int pz=-xx;
	for (j=0;j<=lm+xx;j++) f[j]=inf;
	f[xx]=0;
	for (i=1;i<=n;i++)
	{
		if(s[i]>=0)
		{
			for(j=lm;j-s[i]>=pz;j--)
			 if(f[j-s[i]+xx]!=inf)
			  f[j+xx]=max(f[j+xx],f[j-s[i]+xx]+q[i]);
		}
		else
		{
			for(j=pz;j-s[i]<=lm;j++)
			 if(f[j-s[i]+xx]!=inf)
				f[j+xx]=max(f[j+xx],f[j-s[i]+xx]+q[i]);
		}
	}
	for (i=xx;i<=lm+xx;i++)
	 if (f[i]>=0) ans=mymax(ans,i-xx+f[i]);
	printf("%d\n",ans);
	return 0;
}


四、[bzoj1655]Dollar Dayz,USACO 2006 Jan

解题思路

f[i]表示数目为i时的划分的方案数

f[i]+=f[i-j];水..但是要打高精度!

[太水了。。代码就不贴了占地]


五、[bzoj1578]Stock Market,USACO 2009 Feb


解题思路

f[i]表示第i天能获得的最多的钱
f[i]=max(f[i-1],g[f[i-1]]);
g[i]表示有i钱时得到的最多的钱。
对每天做01背包,k枚举钱,第j天,第i只股票
g[k]=max(g[k],g[k-p[i][j-1]]+p[i][j]);
能一天一天的买卖股票的原因是,今天买了某只股票,若后来在较高点卖出的话,相当于在这期间以没那么高的价格卖了一次又买了一次。若期间的价格都比今天低,那不如在最低的那天再买,弄回前面这种情况。
最后进行最上面的f[]的转移。

p.s.我好像有一个点还是TLE的。。。

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<iostream>
using namespace std;

const int mx=500000;
int p[60][110],f[110],g[mx+10];
int main()
{
	//freopen("stock.in","r",stdin);
	//freopen("stock.out","w",stdout);
	int n,d,m,i,j,k;
	scanf("%d%d%d",&n,&d,&m);
	for (i=1;i<=n;i++)
	 for (j=1;j<=d;j++)
	  scanf("%d",&p[i][j]);
	f[1]=m;
	for (i=2;i<=d;i++)
	{
		for (j=1;j<=f[i-1];j++) g[j]=j;
		for (j=1;j<=n;j++)
		 if (p[j][i]>p[j][i-1])
		 {
			 for (k=p[j][i-1];k<=f[i-1];k++)
			  if (g[k]<g[k-p[j][i-1]]+p[j][i]) g[k]=g[k-p[j][i-1]]+p[j][i];
		 }
		if (g[f[i-1]]>f[i-1]) f[i]=g[f[i-1]];else f[i]=f[i-1];
	}
	printf("%d\n",f[d]);
	return 0;
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值