序言
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;
}
解题思路
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;
}
解题思路
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;
}