今天放两道动态规划的题,本人心得是:认真寻找规律
第一题
多重部分和问题
有n种不同大小数字ai,每种个mi个。判断是否可以从这些数字之中选出若干使它们的和恰好为k。(1≤n≤100,1≤ai,mi≤100000,1≤k≤100000)
样例输入
3 n
3 5 8 a1 a2 ...
3 2 2 m1 m2 ...
17 k
样例输出
YES
方法一:
dp[i,j](前i种数字是否能加成j)
大家会立即推出此式子:
dp[i,j]=(0≤k≤mi且k*ai≤j时存在使dp[i,j-k*ai]为true的k)
当dp[n,k]=true时就是YES。
那么,时间复杂度就有待考虑,每一个数都要试一次,最多会有100*100000个数,再加n层循环,则
O(n∑imi)=O(100*100*100000)=O(109)
时间完爆,满分是不可能的
方法二
认为dp[i,j]指用前i种数加和得到j时第i种数最多能剩下几个,如果加不到则为-1,
①.若前i-1个数可加到j,则直接留下mi个数;
②.若无法加到或前i-1个数不可加到j-ai,则-1;
③.否则,...
∴ mi (dp[i,j]≥0)
dp[i,j]=-1 (j<ai或者dp[i+1,j-ai]≤0)
dp[i+1,j-ai] (其它)
程序如下:
var
  dp,a,m:array[0..100000]of longint;
  n,i,j,k:longint;
begin
  read(n);
  for i:=1 to n do
    read(a[i]);
  for i:=1 to n do
    read(m[i]);
  read(k);
  for i:=1 to k do dp[i]:=0;
  for i:=1 to n do
    for j:=0 to k do
      if dp[j]>=0 then dp[j]:=m[i]
      else if (j<a[i])or(dp[j-a[i]]<=0) then dp[j]:=-1
      else dp[j]:=dp[j-a[i]]-1;
  if dp[k]>=0 then write('YES')
  else write('NO');
end.
C++略,
第二题(poj7624)
山区建小学
- 总时间限制:
 - 1000ms
 - 内存限制:
 - 65536kB
 
- 描述
 - 
政府在某山区修建了一条道路,恰好穿越总共m个村庄的每个村庄一次,没有回路或交叉,任意两个村庄只能通过这条路来往。已知任意两个相邻的村庄之间的距离为di(为正整数),其中,0 < i < m。为了提高山区的文化素质,政府又决定从m个村中选择n个村建小学(设 0 < n < = m < 500 )。请根据给定的m、n以及所有相邻村庄的距离,选择在哪些村庄建小学,才使得所有村到最近小学的距离总和最小,计算最小值。
 - 输入
 - 第1行为m和n,其间用空格间隔
第2行为(m-1) 个整数,依次表示从一端到另一端的相邻村庄的距离,整数之间以空格间隔。
例如
10 3
2 4 6 5 2 4 3 1 3
表示在10个村庄建3所学校。第1个村庄与第2个村庄距离为2,第2个村庄与第3个村庄距离为4,第3个村庄与第4个村庄距离为6,...,第9个村庄到第10个村庄的距离为3。 - 输出
 - 各村庄到最近学校的距离之和的最小值。
 - 样例输入
 - 
10 2 3 1 3 1 1 1 1 1 3
 - 样例输出
 - 
18
 
刚开始以为这题很弱,可是画了画,就------;
先从简单的开始
①.当我要在第i个村庄到第j个村庄建1个学校,最小值怎么求?
大家应该知道,把学校放在村庄之中,总共走的路才最少,小学初中数学奥赛都有讲到,
那么,最小值怎么求?见下图:
1.五个点:.____.____.____.____. ,则最小路程:
____ ____ ____ ____
____ ____
发现 ____ ____ ____ ____ ____ ____ ____ ____
____ (靠左的) + ____ (靠右的) — (第一层中间两个线段)
____ 为两点间距离,每个距离是不一样的
所以可知p[i,j]=p[i,j-1]+p[i+1,j]-p[i+1,j-1](j-i为偶数时)
2.p[i,j]=p[i,j-1]+p[i+1,j]-p[i+1,j-1]+a[(j+i) div 2](j-i为奇数时)
红色部分为中间的线段,如果不仔细,极其难发现!!!请自行画图探究;
1个学校处理完,再处理k个:
dp[i(前i个山村),k]=min(dp[t(k-1≤t≤i-1),k-1]+p[t+1,i]
相信大家刚处理的时候会出现错误,或O(5005)或O(5004)的时间效率,此时要学会减少循环次数,让
dp变成二维。此题用到两次dp,一次预处理,一次为主要部分,有难度,让我耗费1个小时多!
那么,学会降层,程序如下:
var
  a:array[1..500]of longint;
  dp,p:array[1..500,1..500]of longint;
  n,i,j,k,m:longint;
function min(a,b:longint):longint;
begin
  if a<b then exit(a) else exit(b);
end;
begin
  read(n,m);
  for i:=1 to n-1 do
    read(a[i]);
  for i:=0 to n do
    for j:=1 to n do
      if j+i<=n then
        begin
          if i=0 then p[j,j]:=0
          else if i=1 then p[j,j+i]:=a[j]
          else if i mod 2=0 then p[j,j+i]:=p[j,j+i-1]+p[j+1,j+i]-p[j+1,j+i-1]
          else p[j,j+i]:=p[j,j+i-1]+p[j+1,j+i]-p[j+1,j+i-1]+a[(j+j+i) div 2];
        end;
  //一种非常好的处理方法,在循环变量下做文章,使预处理得以成功进行
  for i:=1 to n do
    for k:=1 to min(i,m) do
      if k=i then dp[i,k]:=0
      else if k=1 then dp[i,k]:=p[1,i]
      else
        begin
          dp[i,k]:=maxlongint;
          for j:=i-1 downto k-1 do
            dp[i,k]:=min(dp[i,k],dp[j,k-1]+p[j+1,i]);
        end;
  writeln(dp[n,m]);
end.
#include<iostream>
using namespace std;
int min(int a,int b)
{
    if (a<b) return a; else return b;
 } 
int a[501],dp[501][501],p[501][501],n,i,j,k,m;
int main()
{
    cin>>n>>m;
    for (i=1;i<=n-1;i++) cin>>a[i];
    for (i=0;i<=n;i++)
      for (j=1;j<=n;j++)
        {
            if (j+1<=n)
              {
                  if (i==0) p[i][j]=0;
                  else if (i==1) p[j][j+i]=a[j];
                  else if (i%2==0) p[j][j+i]=p[j][j+i-1]+p[j+1][j+i]-p[j+1][j+i-1];
                else p[j][j+i]=p[j][j+i-1]+p[j+1][j+i]-p[j+1][j+i-1]+a[(j+j+i)/2];
              }
        }
    for (i=1;i<=n;i++)
      for (k=1;k<=min(i,m);k++)
        if (k==i) dp[i][k]=0;
        else if (k==1) dp[i][k]=p[1][i];
        else
          {
            dp[i][k]=100000000;
            for (j=i-1;j>=k-1;j--)
              dp[i][k]=min(dp[i][k],dp[j][k-1]+p[j+1][i]);    
          }
    cout<<dp[n][m];
}
作为好人,我把pascal,C++都上传,但别盲目复制,仔细体会,大家放心,两个程序都已AC。
                    
                

                
            
        
浙公网安备 33010602011771号