贪心之多机调度问题
2017-06-12 19:29
260 查看
题目来自洛谷1190
供水量相等,均为 1。
现在有 n 名同学准备接水,他们的初始接水顺序已经确定。将这些同学按接水顺序从 1到 n 编号,i 号同学的接水量为 wi。接水开始时,1 到 m 号同学各占一个水龙头,并同时打开水龙头接水。当其中某名同学 j 完成其接水量要求 wj后,下一名排队等候接水的同学 k马上接替 j 同学的位置开始接水。这个换人的过程是瞬间完成的,且没有任何水的浪费。即j 同学第 x 秒结束时完成接水,则 k 同学第 x+1 秒立刻开始接水。若当前接水人数 n’不足 m,则只有 n’个龙头供水,其它 m−n’个龙头关闭。
现在给出 n 名同学的接水量,按照上述接水规则,问所有同学都接完水需要多少秒。
输入文件名为 water.in。
第 1 行 2 个整数 n 和 m,用一个空格隔开,分别表示接水人数和龙头个数。
第 2 行 n 个整数 w1、w2、……、wn,每两个整数之间用一个空格隔开,wi表示 i 号同
学的接水量。
输出格式:
输出文件名为 water.out。
输出只有一行,1 个整数,表示接水所需的总时间。
但是这一题中因为已经确定了接水顺序,所以省去了排序。
以上代码的好处是简洁,坏处就是时间复杂度较高。一样的题目放在codevs上就过不去。可以用优先队列优化
(这一题需要排序)
或者用线段树优化:
事实证明,线段树是这三个代码中最快的。
问题描述:
学校里有一个水房,水房里一共装有 m 个龙头可供同学们打开水,每个龙头每秒钟的供水量相等,均为 1。
现在有 n 名同学准备接水,他们的初始接水顺序已经确定。将这些同学按接水顺序从 1到 n 编号,i 号同学的接水量为 wi。接水开始时,1 到 m 号同学各占一个水龙头,并同时打开水龙头接水。当其中某名同学 j 完成其接水量要求 wj后,下一名排队等候接水的同学 k马上接替 j 同学的位置开始接水。这个换人的过程是瞬间完成的,且没有任何水的浪费。即j 同学第 x 秒结束时完成接水,则 k 同学第 x+1 秒立刻开始接水。若当前接水人数 n’不足 m,则只有 n’个龙头供水,其它 m−n’个龙头关闭。
现在给出 n 名同学的接水量,按照上述接水规则,问所有同学都接完水需要多少秒。
输入输出
输入格式:输入文件名为 water.in。
第 1 行 2 个整数 n 和 m,用一个空格隔开,分别表示接水人数和龙头个数。
第 2 行 n 个整数 w1、w2、……、wn,每两个整数之间用一个空格隔开,wi表示 i 号同
学的接水量。
输出格式:
输出文件名为 water.out。
输出只有一行,1 个整数,表示接水所需的总时间。
问题分析
这个问题是NP完全问题,还没有有效的解法(求最优解),但是可以用贪心选择策略设计出较好的近似算法(求次优解)。当n<=m时,只要将作业时间区间分配给作业即可;当n>m时,首先将n个作业从大到小排序,然后依此顺序将作业分配给空闲的处理机。也就是说从剩下的作业中,选择需要处理时间最长的,然后依次选择处理时间次长的,直到所有的作业全部处理完毕,或者机器不能再处理其他作业为止。如果我们每次是将需要处理时间最短的作业分配给空闲的机器,那么可能就会出现其它所有作业都处理完了只剩所需时间最长的作业在处理的情况,这样势必效率较低。在下面的代码中没有讨论n和m的大小关系,把这两种情况合二为一了。但是这一题中因为已经确定了接水顺序,所以省去了排序。
代码:
#include<iostream> #include<cstdio> #include<algorithm> #include<iterator> using namespace std; int n,m,wi[100001],mac[1000001]; bool cmp(const int &x,const int &y) { return x > y; } int main() { cin>>n>>m; for(int i=1;i<=n;i++) scanf("%d",&wi[i]); // for(int i=1;i<=n;i++) cout<<wi[i]<<" "; // cout<<endl; for(int i=1;i<=n;i++) { *min_element(mac,mac+m) += wi[i];//这两个函数的返回值是迭代器(指针) } cout<<*max_element(mac,mac+m); return 0 4000 ; }
以上代码的好处是简洁,坏处就是时间复杂度较高。一样的题目放在codevs上就过不去。可以用优先队列优化
(这一题需要排序)
#include<iostream> #include<algorithm> #include<queue> #include<vector> #include<cstdio> #include<functional> using namespace std; int wi[1000001]; bool cmp(const int &x,const int &y) { return x > y; } int main() { priority_queue < int,vector<int>,greater<int> > q; int m,n,i,x=1,o=1,mark=0,left=0; cin>>n>>m; for(int i=1;i<=n;i++) scanf("%d",&wi[i]); sort(wi+1,wi+n+1,cmp); if(n <= m) { m = n; } else left=n-m; for(int i=1;i<=m;i++) { q.push(wi[o++]); } for(int i=1;i<=left;i++) { x = wi[o++]; int a=q.top(); q.pop(); a += x; q.push(a); } int ans; while( !q.empty() ) { ans=q.top(); q.pop(); } cout<<ans; return 0; }
或者用线段树优化:
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<functional> using namespace std; int n,m,wi[1000001],tree[100001*4],maxx=-1; bool cmp(const int &x,const int &y) { return x > y; } void build(int node,int l,int r) { if(l == r) { tree[node]=wi[l]; maxx=max(maxx,tree[node]); return; } int m=(l+r)/2; build(node*2,l,m); build(node*2+1,m+1,r); tree[node]=min(tree[node*2],tree[node*2+1]); } void update(int node,int l,int r,int w) { if(l == r) { tree[node] += w; maxx=max(maxx,tree[node]); // cout<<node<<' '<<tree[node]<<' '<<maxx<<endl;检查更新是否正确 return; } int m=(l+r)/2; if(tree[node] == tree[node*2]) update(node*2,l,m,w); else update(node*2+1,m+1,r,w); tree[node]=min(tree[node*2],tree[node*2+1]); } int main() { scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) { scanf("%d",&wi[i]); } sort(wi+1,wi+n+1,cmp); // for(int i=1;i<=n;i++) printf("%d ",wi[i]); maxx=wi[1]; if( n <= m) cout<<maxx; else { build(1,1,m); // for(int i=1;i<=5;i++) printf("%d ",tree[i]);检查建树是否正确 for(int i=m+1;i<=n;i++) update(1,1,m,wi[i]); cout<<maxx; } return 0; }
事实证明,线段树是这三个代码中最快的。