序言
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 "); else printf("%I64d ",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 ",i); for (j=a[n].a;j>=0;j--) if (f[j]) break; printf("%d ",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 ",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 ",f[d]); return 0; }