挑战程序设计竞赛 2.3 记录结果再利用的“动态规划”
【Summarize】
1.对于多重背包问题,可以通过记录次数,转化为有限制的完全背包来减少一层枚举量
2.在类背包问题中,可以计算单次循环能产生影响的上界容量,以减少枚举量,将矩形复杂度转为梯形复杂度
3.严格单调要求问题可以通过a[i]转a[i]-i转化为非严格单调问题
4.对于下标可能出现负数的背包问题,可以以容量为原点,双倍容量为上界
POJ 3176:Cow Bowling
/* 数塔问题 */ #include <cstdio> #include <algorithm> using namespace std; int n,a[400][400]; int main(){ scanf("%d",&n); for(int i=1;i<=n;i++){ for(int j=1;j<=i;j++)scanf("%d",&a[i][j]); } for(int i=n-1;i;i--){ for(int j=1;j<=i;j++)a[i][j]+=max(a[i+1][j+1],a[i+1][j]); }printf("%d\n",a[1][1]); return 0; }
POJ 2229:Sumsets
/* 求将一个数列划分为2的幂次相加的方案数 */ #include <cstdio> int dp[1000010],n; int main(){ dp[1]=1; for(int i=2;i<=1000000;i++){ if(i&1)dp[i]=dp[i-1]; else dp[i]=(dp[i>>1]+dp[i-1])%1000000000; }scanf("%d",&n); printf("%d\n",dp[n]); return 0; }
POJ 2385:Apple Catching
/* 有两棵树,可以在其树下左右移动,一开始在编号为1的树 每个时刻会有一个苹果从其中一棵树上掉下来,最多移动w次,问最多可以接到多少苹果 */ #include <cstdio> #include <algorithm> using namespace std; int dp[35],T,W,x,ans=0; int main(){ scanf("%d%d",&T,&W); for(int i=1;i<=T;i++){ for(int j=1;j<=W;j++){ dp[j]=max(dp[j-1],dp[j]); }scanf("%d",&x); for(int j=0;j<=W;j++){ if(((j+1)&1)==(x&1))dp[j]++; } }for(int i=0;i<=W;i++)ans=max(ans,dp[i]); return printf("%d\n",ans),0; }
POJ 3616:Milking Time
/* 给出每段允许挤牛奶的时间段起末和其获得的牛奶, 在每次挤奶结束后要休息r单位时间, 问最多能获得的牛奶量 */ #include <cstdio> #include <algorithm> using namespace std; const int N=1005; int n,m,r,dp[2000005]; struct data{int st,en,num;}p[N]; bool cmp(data a,data b){return a.st<b.st;} int main(){ scanf("%d%d%d",&n,&m,&r); for(int i=1;i<=m;i++){ scanf("%d%d%d",&p[i].st,&p[i].en,&p[i].num); p[i].en+=r; }sort(p+1,p+m+1,cmp); int cnt=1; for(int i=0;i<=n+r;i++){ dp[i]=max(dp[i],dp[i-1]); while(i==p[cnt].st){ dp[p[cnt].en]=max(dp[p[cnt].st]+p[cnt].num,dp[p[cnt].en]); cnt++; } }printf("%d\n",dp[n+r]); return 0; }
POJ 3280:Cheapest Palindrome
/* 一个字符串,添加或者删除字符,让其成为回文 删除或者添加每种字符都有不同的代价,求最小代价 */ #include <cstdio> #include <algorithm> using namespace std; int dp[2010][2010],a[30],n,m,x,y; char s[2010],c[2]; int main(){ scanf("%d%d %s",&m,&n,s); for(int i=1;i<=m;i++){ scanf(" %s%d%d",c,&x,&y); a[c[0]-'a']=min(x,y); }for(int i=1;i<n;i++)for(int j=i;j>=0;j--){ dp[j][i]=min(dp[j+1][i]+a[s[j]-'a'],dp[j][i-1]+a[s[i]-'a']); if(s[i]==s[j])dp[j][i]=min(dp[j][i],dp[j+1][i-1]); }printf("%d\n",dp[0][n-1]); return 0; }
POJ 1742:Coins
/* 给出每个硬币的价值和数量,求凑成给出数额的方案数 多重背包问题,在枚举每个物品的时候记录在凑出一定份额时的使用次数替代数量枚举 使得复杂度为O(nm) */ #include <cstdio> #include <cstring> const int N=100005; int dp[N],tot[N],A[105],C[105],n,m,ans; int main(){ while(~scanf("%d%d",&n,&m)&&n+m){ for(int i=1;i<=n;i++)scanf("%d",&A[i]); for(int i=1;i<=n;i++)scanf("%d",&C[i]); memset(dp,0,sizeof(dp));dp[ans=0]=1; for(int i=1;i<=n;i++){ memset(tot,0,sizeof(tot)); for(int j=A[i];j<=m;j++){ if(!dp[j]&&dp[j-A[i]]&&tot[j-A[i]]<C[i]){ tot[j]=tot[j-A[i]]+(dp[j]=1);ans++; } } }printf("%d\n",ans); }return 0; }
POJ 3046:Ant Counting
/* 给出几个不同数字,问能组成指定大小的几个不同的集合 统计集合大小从S到B的答案。 */ #include <cstdio> #include <cstring> using namespace std; int dp[100005],T,A,S,B,x,a[1005],tot; const int mod=1000000; int main(){ scanf("%d%d%d%d",&T,&A,&S,&B); memset(a,0,sizeof(a)); memset(dp,0,sizeof(dp)); for(int i=1;i<=A;i++){ scanf("%d",&x); a[x]++; }dp[0]=1;tot=0; for(int i=1;i<=T;i++){ tot+=a[i]; for(int j=tot;j;j--){ for(int k=1;k<=a[i];k++){ if(j<k)break; dp[j]=(dp[j]+dp[j-k])%mod; } } }int ans=0; for(int i=S;i<=B;i++)ans=(ans+dp[i])%mod; printf("%d\n",ans); return 0; }
POJ 3181:Dollar Dayz
/* 求用1~k的数组成n的非重方案数 因为答案超过了longlong,所以我们用两个longlong来记录 */ #include <cstdio> int n,k; const long long base=1000000000000000000LL; long long dp1[1100],dp[1100]; int main(){ scanf("%d%d",&n,&k); dp[0]=1; for(int i=1;i<=k;i++){ for(int j=i;j<=n;j++){ dp1[j]=dp1[j-i]+dp1[j]+(dp[j]+dp[j-i])/base; dp[j]=(dp[j]+dp[j-i])%base; } }if(dp1[n])printf("%lld",dp1[n]); printf("%lld\n",dp[n]); return 0; }
POJ 1065:Wooden Sticks
/* 每台机器只能切两项属性都是非减的木头序列,问至少需要多少台机器 将所有木头排序之后对单个属性逆向求最长单调递增序列长度就是答案 */ #include <cstdio> #include <algorithm> #include <climits> using namespace std; struct data{int a,b;}p[5010]; bool cmp(data a,data b){return a.a<b.a||a.a==b.a&&a.b<b.b;} int T,n,dp[5010]; int main(){ scanf("%d",&T); while(T--){ scanf("%d",&n); for(int i=0;i<n;i++)scanf("%d%d",&p[i].a,&p[i].b); sort(p,p+n,cmp); int ans=0; for(int i=1;i<=n;i++)dp[i]=INT_MAX; for(int i=n-1;i>=0;i--){ int t=lower_bound(dp+1,dp+n+1,p[i].b)-dp; dp[t]=p[i].b; ans=max(ans,t); }printf("%d\n",ans); }return 0; }
POJ 1631:Bridging signals
/* 有两排点,编号均为1~n,现在告诉你左边第i个点和右边ai号点连线 请选择一些线,使得其不相交。 我们发现如果线段不相交的话,那么选出的ai序列子序列单调递增, 因此只要求出最长单调递增序列即可 */ #include <cstdio> #include <algorithm> #include <climits> using namespace std; int T,n,x,dp[100010]; int main(){ scanf("%d",&T); while(T--){ scanf("%d",&n);int ans=0; for(int i=1;i<=n;i++)dp[i]=INT_MAX; for(int i=1;i<=n;i++){ scanf("%d",&x); int t=lower_bound(dp+1,dp+n+1,x)-dp; dp[t]=x; ans=max(ans,t); }printf("%d\n",ans); }return 0; }
POJ 3666:Making the Grade
/* 给出一个数列,请你经过调整使得其成为非严格单调递增的数列, 调整就是给某些位置加上或者减去某个数, 调整的代价是加上或者减去的数的绝对值之和,请你输出最小代价。 题解:我们发现每次调整, 都是调整某个数字为原先数列中存在的数字,最后才是最优的, 所以,我们设DP[i][j]表示前i个数字,最后一个数为原先数列排序后第j大的数字的最小代价, 那么做一遍n2的DP就能够获得答案, */ #include <cstdio> #include <algorithm> using namespace std; const int N=3010; int n,a[N],b[N]; long long dp[N][N]; int main(){ scanf("%d",&n); for(int i=1;i<=n;i++){ scanf("%d",a+i); b[i]=a[i]; }sort(b+1,b+n+1); for(int i=1;i<=n;i++){ long long mn=dp[i-1][1]; for(int j=1;j<=n;j++){ mn=min(mn,dp[i-1][j]); dp[i][j]=abs(a[i]-b[j])+mn; } }long long ans=dp[n][1]; for(int i=1;i<=n;i++)ans=min(ans,dp[n][i]); printf("%I64d\n",ans); return 0; }
POJ 2392:Space Elevator
/* 给出砖块的高度和数量,以及允许放的最大高度,求最高能堆积的高度 因为有最大高度限制,所以先进行排序,然后就能进行背包策略了, 对于数量的限制,可以用多重背包转限制完全背包处理。 */ #include <cstdio> #include <algorithm> #include <cstring> using namespace std; int tot[40010],dp[40010],ans=0,n; struct data{int h,a,c;}p[40010]; bool cmp(data a,data b){return a.a<b.a;} int main(){ scanf("%d",&n); for(int i=0;i<n;i++)scanf("%d%d%d",&p[i].h,&p[i].a,&p[i].c); sort(p,p+n,cmp); dp[0]=1; for(int i=0;i<n;i++){ memset(tot,0,sizeof(tot)); for(int j=p[i].h;j<=p[i].a;j++){ if(!dp[j]&&dp[j-p[i].h]&&tot[j-p[i].h]<p[i].c){ dp[j]=1; tot[j]=tot[j-p[i].h]+1; ans=max(ans,j); } } }printf("%d\n",ans); return 0; }
POJ 2184:Cow Exhibition
/* 每件物品有两个属性,存在负数,选取一下物品, 最大化两个属性的和,且任一属性的和不能为负数 */ #include <cstdio> #include <climits> #include <algorithm> using namespace std; const int N=200010,base=100000; int n,dp[N],a[N],b[N]; int main(){ while(~scanf("%d",&n)){ for(int i=1;i<=n;i++)scanf("%d%d",a+i,b+i); for(int i=0;i<N;i++)dp[i]=INT_MIN; dp[base]=0; for(int i=1;i<=n;i++){ if(a[i]>0){ for(int j=base<<1;j>=a[i];j--){ if(dp[j-a[i]]>INT_MIN)dp[j]=max(dp[j],dp[j-a[i]]+b[i]); } }else{ for(int j=0;j<=(base<<1)+a[i];j++){ if(dp[j-a[i]]>INT_MIN)dp[j]=max(dp[j],dp[j-a[i]]+b[i]); } } }int ans=INT_MIN; for(int i=base;i<=base<<1;i++){ if(dp[i]>=0)ans=max(i-base+dp[i],ans); }printf("%d\n",ans); }return 0; }
愿你出走半生,归来仍是少年