集合问题--鱼塘钓鱼--飞行员

分类: 基本数据结构 |
一)openjudge集合问题
有一组正整数,总数不超过1000,其中最大值记为M。现要将它们划分成N个集合,使得每个集合的元素之和与M的差的绝对值的和最小。
集合A中当前各元素之和记为SUM(A),称为A的负荷;SUM(A)与M之差的绝对值称为A的负荷与理想负荷的偏差,简称为A的偏差。把这些整数划分成
N个集合的方法是:按照从大到小的顺序,依次为每个整数分别选择一个集合;确定一个整数所属集合时,先计算各集合的负荷,将该整数分配给负荷最小的那个集
合。
求使得各集合的偏差之和最小的划分方案中,集合的数目N。如果这样的方案不止一种,则输出各方案中,集合数最大的那种方案的集合数N。
输入
共输入K+1个整数。
其中第一个整数是K代表要划分的整数总数,后面依次是K个整数的值。K不超过1000。
输出
一个整数,代表集合数N。
样例输入一
8
2 4 9
12 16
80 28
72
样例输出
3
样例输入二
4
5 5 5 6
样例输出二:
4
集合个数N的取值在1~K之间,从小到大枚举所有可能,对于每种可能,建立一个优先队列最小堆,按题意将所有元素从大到小依次放入当前负荷最小的集合中,全部元素放完后计算偏差之和,从而找出最小偏差和的集合个数nmin. 由于采用从小到大枚举,故同解的条件下较大的N会覆盖较小的N,故所求解为集合数最大的方案。
#include "bits/stdc++.h"
using namespace std;
int n,a[1010],minn=2e9,ans=0;
bool flag;
priority_queue < int,vector < int >,greater < int > > q;
int comp(int x,int y)
{
return x > y;
}
main()
{
cin>>n;
for (int i=1;i<=n;i++) cin>>a[i];
sort(a+1,a+n+1,comp);
for (int i=1;i<=n;i++)
{//枚举分成i个组的可能性;
int total=0;
for (int j=1;j<=i;j++) q.push(0);//每个组开始的时侯都是空
for (int j=1;j<=n;j++)//枚举每个元素
{//找到当前最小的组,把当前元素a[j]添加进去
int k=q.top();
q.pop();
q.push(k+a[j]);
}
for(int j=1;j<=i;j++)
total+=abs(a[1]-q.top()),q.pop();//题意,每组sum与max的差的和;
if (minn >= total) ans=i,minn=total;//标记当前的最优记录
}
cout<<ans;
}
这题有一个“简易的错误算法”。简易是说它可以骗过openjudge,AC! 错误的原因,是它是错的。
sum指总和,max是最大值, bs=sum/max;
如果 sum是max的倍数 则ans=bs;
如果 2*bs > max 则ans=bs+1
否则仍然是bs;
错误的原因就在于,如果不通过堆的统计,则sum / or %的意思,是它可以被无限分割,但是条件未必。
第二个条件并不能弥补上述逻辑的缺失,事实上它没有意义,2*bs 和max之间没有逻辑关系
二)455.鱼塘钓鱼(fishing)
【问题描述】
有N个鱼塘排成一排(N<100),每个鱼塘中有一定数量的鱼,例如:N=5时,如下表:
http://s6/mw690/005U6Roxzy7nZjUAF25e5&690
即:在第1个鱼塘中钓鱼第1分钟内可钓到10条鱼,第2分钟内只能钓到8条鱼,……,第5分钟以后再也钓不到鱼了。从第1个鱼塘到第2个鱼塘需要3分钟,从第2个鱼塘到第3个鱼塘需要5分钟,……
【编程任务】
给出一个截止时间T(T<1000),设计一个钓鱼方案,从第1个鱼塘出发,希望能钓到最多的鱼。
假设能钓到鱼的数量仅和已钓鱼的次数有关,且每次钓鱼的时间都是整数分钟。
【输入格式】
输入文件共5行,分别表示:
第1行为N;
第2行为第1分钟各个鱼塘能钓到的鱼的数量,每个数据之间用一空格隔开;
第3行为每过1分钟各个鱼塘钓鱼数的减少量,每个数据之间用一空格隔开;
第4行为当前鱼塘到下一个相邻鱼塘需要的时间;
第5行为截止时间T;
【输出格式】输出文件仅一个整数(不超过231-1),表示你的方案能钓到的最多的鱼。
【输入样例】
5
10 14 20 16 9
2
4
6 5 3
3
5
4 4
14
【输出样例】 76
样例解释:
第一个塘钓1分钟:得10, 然后到第2个塘,共花4分钟
第二个塘钓2分钟,得14,10,到第3个塘;共化7分钟;
第三个塘钓3分钟 20,14,8;
10+24+42=76;
【知识准备】
最优化原理、贪心法、动态规划、用堆结构实现贪心。
【问题分析】
算法一:
我们可以这样想:如果知道了取到最大值的情况下,人最后在第i个鱼塘里钓鱼,那么用在路上的时间是固定的,因为我们不会先跑到第i个鱼塘里钓一分钟后再返回前面的鱼塘钓鱼,这样把时间浪费在路上显然不划算,再说在你没到某个鱼塘里去钓鱼之前,这个塘里的鱼也不会跑掉(即数量不会减少)。所以这时我们是按照从左往右的顺序钓鱼的,也可以看成路上是不需要时间的,(注:指时间由独立的参数递减控制,程序不必考虑),即人可以自由在1~i个鱼塘之间来回走,只要尽可能选取钓到的鱼多的地方就可以了,这就是我们的贪心思想。其实,这个贪心思想并不是模拟钓鱼的过程,只是统计出在各个鱼塘钓鱼的次数。程序实现时,只要分别枚举钓鱼的终
点鱼塘(从鱼塘1到鱼塘n),每次按照上述贪心思想确定在哪些鱼塘里钓鱼,经过n次后确定后最终得到的一定是最优方案。
红色字部分最容易引人误会:程序设置了一个到鱼塘所需时间的前缀和,作为限制到达某处鱼塘的条件;在此限制下,就可以把在此到达范围内的所有鱼塘之间,都看成是不花时间的。然后,就枚举每一个鱼塘,选出其中剩鱼最多的钓。不必考虑它的次序——>由于不可能走回头路,所以顺路下来的数量,就是真实的次序。
这里所谓的贪心,其实只是动规方法(静态记录数组下的搜索)下采用的方式,它本质上已经是动规。
贪心参考:
using namespace std;
int a[105],b[105],c[105],v[105];
int main()
{
int n;
cin>>n;
for (int i=1;i<=n;++i)
cin>>a[i];//塘初始数
for (int i=1;i<=n;++i)
cin>>b[i];//塘减数
for (int
i=1;i<=n-1;++i)
{
cin>>c[i];
c[i]=c[i]+c[i-1];
}//时间消耗
int maxxx=0;
int t;cin>>t;
for (int
i=1;i<=n;++i)
{
//枚举每个池塘
for (int
j=1;j<=i;++j) v[j]=a[j]+b[j];
//因为剩下的鱼 y=v[k]-b[k],所以加上b[j],为了假定所有的鱼都可选。
int
s=t-c[i-1];//到这个塘后剩下的总时间
int ans=0;
for (int j=1;j<=s;++j)//在时间限度内的枚举,这是动规的方法;
{
int
maxx=0,xh,y;
for (int k=1;k<=i;++k)
{//枚举在当前之前的几个鱼塘
y=v[k]-b[k];//剩下的鱼
if
(y>maxx) maxx=y, xh=k;//谁剩下的鱼多,到那里钓鱼
}//在指定的时间限定内,扫描每个池塘的最大值;
//形成时间限度j *
每个池塘的枚举;
v[xh]=v[xh]-b[xh];//减去塘中减少的鱼量,以备下一个枚举;
ans+=maxx;
}//end
for;
maxxx=max(maxxx,ans);
}
cout<<maxxx;
}
算法二:
其实,这道题是考虑最优性问题的,所以我们也可以用动态规划来解决,假设用Opt[t][n]来表示第t分钟时,人在第n个鱼塘里钓鱼,最多所能钓到的鱼数。则:
Opt[t][n] =Maxinum{Opt[t-k][n-1]+S};
穷举k,S为t-k+1到t之间,除去从第n-1的鱼塘走到第n个鱼塘的时间,在第n个鱼塘中可以钓到的鱼数。
动规参考:
program fishing2;
//动态规划
const maxT = 1000;
maxF = 1000;
maxN = 100;
type nodeP = record
输入
其中第一个整数是K代表要划分的整数总数,后面依次是K个整数的值。K不超过1000。
输出
样例输入一
样例输出
样例输入二
4
5 5 5 6
样例输出二:
集合个数N的取值在1~K之间,从小到大枚举所有可能,对于每种可能,建立一个优先队列最小堆,按题意将所有元素从大到小依次放入当前负荷最小的集合中,全部元素放完后计算偏差之和,从而找出最小偏差和的集合个数nmin. 由于采用从小到大枚举,故同解的条件下较大的N会覆盖较小的N,故所求解为集合数最大的方案。
#include "bits/stdc++.h"
using namespace std;
int n,a[1010],minn=2e9,ans=0;
bool flag;
priority_queue < int,vector < int >,greater < int > > q;
int comp(int x,int y)
{
}
main()
{
}
这题有一个“简易的错误算法”。简易是说它可以骗过openjudge,AC! 错误的原因,是它是错的。
sum指总和,max是最大值, bs=sum/max;
如果 sum是max的倍数 则ans=bs;
如果 2*bs > max 则ans=bs+1
否则仍然是bs;
错误的原因就在于,如果不通过堆的统计,则sum / or %的意思,是它可以被无限分割,但是条件未必。
第二个条件并不能弥补上述逻辑的缺失,事实上它没有意义,2*bs 和max之间没有逻辑关系
二)455.鱼塘钓鱼(fishing)
【问题描述】
http://s6/mw690/005U6Roxzy7nZjUAF25e5&690
即:在第1个鱼塘中钓鱼第1分钟内可钓到10条鱼,第2分钟内只能钓到8条鱼,……,第5分钟以后再也钓不到鱼了。从第1个鱼塘到第2个鱼塘需要3分钟,从第2个鱼塘到第3个鱼塘需要5分钟,……
【编程任务】
给出一个截止时间T(T<1000),设计一个钓鱼方案,从第1个鱼塘出发,希望能钓到最多的鱼。
假设能钓到鱼的数量仅和已钓鱼的次数有关,且每次钓鱼的时间都是整数分钟。
【输入格式】
输入文件共5行,分别表示:
第1行为N;
第2行为第1分钟各个鱼塘能钓到的鱼的数量,每个数据之间用一空格隔开;
第3行为每过1分钟各个鱼塘钓鱼数的减少量,每个数据之间用一空格隔开;
第4行为当前鱼塘到下一个相邻鱼塘需要的时间;
第5行为截止时间T;
【输出格式】输出文件仅一个整数(不超过231-1),表示你的方案能钓到的最多的鱼。
【输入样例】
5
10 14 20 16 9
14
【输出样例】 76
第一个塘钓1分钟:得10, 然后到第2个塘,共花4分钟
第二个塘钓2分钟,得14,10,到第3个塘;共化7分钟;
第三个塘钓3分钟
10+24+42=76;
【知识准备】
【问题分析】
算法一:
红色字部分最容易引人误会:程序设置了一个到鱼塘所需时间的前缀和,作为限制到达某处鱼塘的条件;在此限制下,就可以把在此到达范围内的所有鱼塘之间,都看成是不花时间的。然后,就枚举每一个鱼塘,选出其中剩鱼最多的钓。不必考虑它的次序——>由于不可能走回头路,所以顺路下来的数量,就是真实的次序。
这里所谓的贪心,其实只是动规方法(静态记录数组下的搜索)下采用的方式,它本质上已经是动规。
贪心参考:
using namespace std;
int a[105],b[105],c[105],v[105];
int main()
{
}
算法二:
其实,这道题是考虑最优性问题的,所以我们也可以用动态规划来解决,假设用Opt[t][n]来表示第t分钟时,人在第n个鱼塘里钓鱼,最多所能钓到的鱼数。则:
Opt[t][n] =Maxinum{Opt[t-k][n-1]+S};
穷举k,S为t-k+1到t之间,除去从第n-1的鱼塘走到第n个鱼塘的时间,在第n个鱼塘中可以钓到的鱼数。
动规参考:
program fishing2;
const maxT = 1000;
type nodeP = record