标签:
今天放两道动态规划的题,本人心得是:认真寻找规律
第一题
多重部分和问题
有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)
山区建小学
政府在某山区修建了一条道路,恰好穿越总共m个村庄的每个村庄一次,没有回路或交叉,任意两个村庄只能通过这条路来往。已知任意两个相邻的村庄之间的距离为di(为正整数),其中,0 < i < m。为了提高山区的文化素质,政府又决定从m个村中选择n个村建小学(设 0 < n < = m < 500 )。请根据给定的m、n以及所有相邻村庄的距离,选择在哪些村庄建小学,才使得所有村到最近小学的距离总和最小,计算最小值。
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。
标签:
原文地址:http://www.cnblogs.com/ZZMbk/p/4715218.html