编程挑战题:SPOJ LOPOV
时间:2013-12-05
来源:互联网
为方便大家,现翻译题目重点如下:
现有 N 粒宝石,每粒的重量和价值,分别为 Mi 和 Vi。此外,现有 K 个袋,每个袋的容量为 Ci,但只能放入一粒宝石。
问:能放置在袋内的宝石的总价值,最大是多少?
限制:
1 <= N, K <= 300,000
1 <= Mi, Vi <= 1,000,000
1 <= Ci <= 1,000,000,000
全部数值为正整数。
此外,我亦想知大家用乜嘢算法去解决呢题。我谂唔到现成嘅算法,於是创出(可能一早已经有人谂到/创出/发现)现有方法,感觉速度好似不俗。
更新:现在排第二时间系 0.13,同我嘅时间重有一段距离,但已经拉近好多。
[ 本帖最后由 fitcat07 於 2013-10-28 12:10 PM 编辑 ]
作者: fitcat07 发布时间: 2013-12-05
或者首先讲讲解答嘅方法。
最简单但最慢嘅方法:
0. 将 N 粒宝石价值由大至小排列,初始 answer = 0,i = 0
1. 喺 N 粒宝石中,最大价值嘅一粒系第 i 粒
2. 然后喺 K 个袋中,搵出刚刚可以放置以上最大价值粒宝石。假设系第 m 个袋。即 Cm >= Mi, Cn >= Cm for all 0 <= n < N, Cn >= Mi, n != m。
3. 搵到呢个袋嘅话,将粒石嘅价值 Vi 加入 answer 中,并将呢个袋 Km 移除,K = K - 1
4. i = i + 1
5. 重复步骤 1 直至 i = N
以上方法嘅时间复杂度为 O(N*K)。因为 N 同 K 最大可以系 300,000,所以 O(N*K) = O(300,000*300,000) = O(9x10^12)。时间限制系 1s,即使现今最快嘅 Intel CPU 都唔够时间完成,结果必然系 TLE(Time Limited Exceeded)。
咁点可以加快呢?
作者: fitcat07 发布时间: 2013-12-05
无人有兴趣?
或者首先讲讲解答嘅方法。
最简单但最慢嘅方法:
0. 将 N 粒宝石价值由大至小排列,初始 answer = 0,i = 0
1. 喺 N 粒宝石中,最大价值嘅一粒系第 i 粒
2. 然后 ...

乜要O(n*k)咩? sort 一 sort就变O(n*log n)囉。

作者: code4food 发布时间: 2013-12-05
有兴趣但无时间。



如果将 K 个袋由小至大排列(O(K*log(K)),咁每次搵就唔驶 K 次,而系 log(K) 次,快好多。总时间变成 O(N*log(N) + K*log(K) + N*log(K))。但问题系,当搵到合身嘅袋,要将之移除。有乜快嘅方法,可以移除后,使能保持搵嘅次数保持系 log(K) 呢?
呢个其实系我将会写嘅帖内容。
作者: fitcat07 发布时间: 2013-12-05
无人有兴趣?
或者首先讲讲解答嘅方法。
最简单但最慢嘅方法:
0. 将 N 粒宝石价值由大至小排列,初始 answer = 0,i = 0
1. 喺 N 粒宝石中,最大价值嘅一粒系第 i 粒
2. 然后 ...
I just think a little bit on the bus

input
3 1
4 5
6 6
7 7
10
In this case, the jewellery has largest value should be skipped and the largest total value is 5+6 = 11 and not 7. (Please tell me, if I interpret your meaning incorrectly, )
[ 本帖最后由 assembly.jc 於 2013-10-28 05:39 PM 编辑 ]
作者: assembly.jc 发布时间: 2013-12-05
Step 0:
Sort the N pieces of jewellery: (7, 7), (6, 6), (5, 4) <- first and second of the ordered pair means value and mass, respectively
answer = 0
i = 0
Step 1:
The most valuable is Ni, which is (7, 7). Then, Mi = 7, Vi = 7.
Step 2:
We find a bag Cm with size 10 which is the best fit. (m = 0).
Step 3:
Since we found a best-fit bag m in step 2, answer = answer + Vi = 7.
Take away this bag. Now, K = K - 1 = 0.
Step 4:
i = i + 1 = 1.
Step 5:
We repeat step 1 to 5. Since K = 0, we no longer find any bags fit, step 3 will not be executed.
Hence, answer = 7.
The above steps can be improved: when K = 0, terminate immediately.
作者: fitcat07 发布时间: 2013-12-05
重系咁忙呀...

步骤 0 无错系 O(N*log(N)),但每粒宝石要搵到最合身嘅袋,就要搵 K 次。
作者: code4food 发布时间: 2013-12-05
N = 3, K = 1.
Step 0:
Sort the N pieces of jewellery: (7, 7), (6, 6), (5, 4)
Thanks for detail explanation. As I miss this sentence: "but at most one jewellery piece in each bag", so I think the bag can carry 2 piece of jewllery yesterday (and hence the answer should be 5+6 = 11, not 7), and now I read your post #1 and the description of the problem on SPOJ again, I eventually know why I am confused on your solution

Otherwise, if the bag can be allowed to carry any number of piece of jewellery, this problem become very complex
[ 本帖最后由 assembly.jc 於 2013-10-29 10:56 AM 编辑 ]
作者: assembly.jc 发布时间: 2013-12-05
我话sort一sort,系袋都sort埋。用balanced tree (例如std::set)。建立树嘅时间系O(k * log k), 每次搵(e.g. std::set::lower_bound())个袋同更新(std::set::erase()嘅时间系O(log k),每个袋都装粒宝石 ...
0. 将 N 粒宝石嘅价值由大至小排列,K 个袋依容量由小至大排列,i = 0,j = 0,pq = {},answer = 0
1. 如果 Mi <= Cj,将 Vi 加入 pq,i = i + 1
2. 否则,如果 pq 唔系空,answer = answer + pq.top(),pq.pop()
2a. j = j + 1
3. 重复 1 直至 i >= N 或 j >= K
4. 如 j < K 同埋 pq 唔系空,answer = answer + pq.top(),pq.pop(),j = j + 1,重复 4
时间复杂度:
0. O(N*log(N) + K*log(K))
1 - 4. O(N*log(N) + N*log(N)) (加入完素 pq:N*log(N),取出完素 pq:N*log(N))总时间复杂度:O(3N*log(N) + K*log(K)),但出乎意料结果系 TLE。就系因为咁都唔得,我无再尝试 std::multiset,因为大家时间复杂度相似。
今日先发现原来有一个臭虫,改咗之后(错误地将步骤 2a 归入 2),用 std::priority_queue 时间系 0.11s!
经你一提,今日一试 std::multiset,竟然 AC,时间大约系 0.30s。原来呢题真系咁易(自己谂得过於复杂),早知唔驶花咁多时间去谂其他方法。不过,亦因此谂到暂时最快方法。

作者: fitcat07 发布时间: 2013-12-05
Otherwise, if the bag can be allowed to carry any number of piece of jewellery, this problem become very complex
http://en.wikipedia.org/wiki/Knapsack_problem
作者: fitcat07 发布时间: 2013-12-05
我第一眼都谂到呢个方法,我自以为用 std::set (正确系用 std::multiset)系唔够快,之前无试过。於是我谂用 std::priority_queue 会更快,因为唔须要将所有元素排序,方法系:
0. 将 N 粒宝石嘅价值由大至小 ...
作者: code4food 发布时间: 2013-12-05
Yes, it will become a more complicated version of the normal Knapsack problem (NP-complete):
http://en.wikipedia.org/wiki/Knapsack_problem
Actually, many problems related to combination, such marksix, probability... combination obtains elegant properties in mathematics but not in computer science.
However, anyway, let us return to jewellery problem. and so, the problem now can be transformed to how to find a bag (that has least mass Ci) to hold the largest value Vi of piece of jewellery. Okey, haha, let me guess your best soluation (fastest version). Firstly, let us split this problem into 2 parts:
1. find the largest value of piece of jewellery
2. find the least mass of bag to hold the jewellery
for part 1, the fastest way seems to sort the values of piece of jewellery, or using heap structure. either method has complexity O(n lg n).
for part 2, instead of sorting the mass Ci hold by bags, we may apply the concept of Hash.
For example, Assume 1 <= Ci <= 100 (actually, it is 1 <= Ci <= 1,000,000, 000), we create an array that hold 10 linked-list, each node of linked-link hold the mass of bag Ci. It may look like:
typedef struct _linked_list {
int mass;
struct _linked_list next;
} LinkedList;
LinkedList list[10] = {NULL};

[ 本帖最后由 assembly.jc 於 2013-10-31 02:44 PM 编辑 ]
作者: assembly.jc 发布时间: 2013-12-05
for part 1, the fastest way seems to sort the values of piece of jewellery, or using heap structure. either method has complexity O(n lg n).
作者: fitcat07 发布时间: 2013-12-05
Before you said that, I expected heap may be faster... but anyway, sorting has more choices, it is good chance for me to test the efficient of variety of sorting method, haha

void *ciHash[10][10][10][10][10][10][10][10] = {NULL};
and so, the size of each bucket of Hash can be reduced to 10 and such an array occupied 40 Mb memory. However, I am not sure whether C support 8-dimension array, if it is not, we may allocate a large block of memory firstly (say 40Mb), and calculate the hash code for each Ci by myself.
[ 本帖最后由 assembly.jc 於 2013-11-2 10:44 AM 编辑 ]
作者: assembly.jc 发布时间: 2013-12-05
Before you said that, I expected heap may be faster... but anyway, sorting has more choices, it is good chance for me to test the efficient of variety of sorting method, haha

void *ciHash[10][10][10][10][10][10][10][10] = {NULL};
and so, the size of each bucket of Hash can be reduced to 10 and such an array occupied 40 Mb memory. However, I am not sure whether C support 8-dimension array, if it is not, we may allocate a large block of memory firstly (say 40Mb), and calculate the hash code for each Ci by myself.
No matter how small you set the bucket size (unless it is 1 - which decompose to a 1D array), the worst case scenario does not change - all bags can still fall into a single bucket.
Having said that, it is theoretically not helpful but it doesn't mean in practice it performs poorer. Just like quick sort, the worst case time complexity is O(N^2) but it is fast enough in normal use.
If the test cases were large and/or well-constructed, it would include data for such scenario. In practice, it may not. So, try to implement and submit it, and see...
BTW, an array of size 10^8 of pointer type (assume 32-bit pointer) has a size of 4*10^8 = 4*100MB = 400MB.
作者: fitcat07 发布时间: 2013-12-05
作者: fitcat07 发布时间: 2013-12-05
I think I may misinterpret your method. You defined a pointer for each element in the range 1 - 100,000,000. Each pointer points to the corresponding mass of the bag. When mass x is required, the poin ...
Yes, you got the point, I read the problem again and find that the limit of memory size is 1536Mb, so, as you said that, it can be simply allocated 400Mb memory for an array such as
int ciHash[100000000] = { 0 };
and when the mass x of bag is provided, perform the follow operation
ciHash[x]++;
However, this method is not work, since when a jewellery associated with value Vi need to find a bag, using the Hash, then when ciHash[Vi] == 0, we can not find the mass y quickly such that
y > Vi and ciHash[y] > 0
Maybe I thank it in wrong direction from the begin

Anyway, besides the above method, I also made a wrong concept on finding the bag(please refers the part 2 as mentioned on previous post #12). that is. When the value of all pieces of jewellery have been sorted and the largest value of piece of jewellery (say Vi) is given, we don't need to find the least mass (say Ci, Ci >= Vi of course) of bag to hold the piece of jewellery, any bags that has the mass >= Vi can be chosen and don't affect the final answer. And so the mass of bags may not need to sort.
[ 本帖最后由 assembly.jc 於 2013-11-4 03:12 AM 编辑 ]
作者: assembly.jc 发布时间: 2013-12-05
...Anyway, besides the above method, I also made a wrong concept on finding the bag(please refers the part 2 as mentioned on previous post #12). that is. When the value of all pieces of jewellery have been sorted and the largest value of piece of jewellery (say Vi) is given, we don't need to find the least mass (say Ci, Ci >= Vi of course) of bag to hold the piece of jewellery, any bags that has the mass >= Vi can be chosen and don't affect the final answer. And so the mass of bags may not need to sort.
However, I am afraid you're wrong regarding the sorting of bags is not needed.
Consider this test case:
作者: fitcat07 发布时间: 2013-12-05
However, I am afraid you're wrong regarding the sorting of bags is not needed.
Consider this test case:
2 21 992 65101
If we place the f ...
Yes, you're right, I wrongly assume that larger value of piece of jewellery has larger mass

Otherwise, I am confused by the method on #9 as below:
1. 如果 Mi <= Cj,将 Vi 加入 pq,i = i + 1
2. 否则,如果 pq 唔系空,answer = answer + pq.top(),pq.pop()
2a. j = j + 1
3. 重复 1 直至 i >= N 或 j >= K
4. 如 j < K 同埋 pq 唔系空,answer = answer + pq.top(),pq.pop(),j = j + 1,重复 4
How to handle the follow case?
Input:
2 1
5 5
2 2
4
N = 2, K = 1
0. Sort the value of piece of jewellery in descending order: [(M:5, V:5), (M:2), (V:2)],
Sort the mass of bag in ascending order : [(C:4)]
i=0, j=0, pg={}, anwser = 0
1. M0 = 5 > C0 = 4 => do nothing
2. pg is empty => do nothing
2a. j = j + 1 => j == 1
3. j == K => goto step 4
4. j == k => end
finally, the answer is 0, but the answer should be 2??
[ 本帖最后由 assembly.jc 於 2013-11-5 12:00 PM 编辑 ]
作者: assembly.jc 发布时间: 2013-12-05
Does it iterate the Mi through the descending order of value of piece of jewellery ? In step 3, Is it repeats step 1 to 2a instead of step 1 until i >= N or j >= k?
1. M0 = 5 > C0 = 4 => do nothing
2. pg is empty => do nothing
2a. j = j + 1 => j == 1
3. j == K => goto step 4
4. j == k => end
finally, the answer is 0, but the answer should be 2??

作者: fitcat07 发布时间: 2013-12-05
如你系呢位 bb,可否讲讲你嘅方法,大家交流一下呢?
作者: fitcat07 发布时间: 2013-12-05
Yes, it should repeat step 1 to 2a. I should use pseudo code instead of steps as it should be more clear.Oops, Step 0 was incorrect. The sorting of jewellery should be in "ascending" order of "mass". ...
Last week, I based on your method (post #9) to test the efficiency of variety of sorting algorithms, it includes insertion sort, quick sort, stooge sort, heap sort and radix sort (+count sort) and the result as below:
insertion sort: TLE
quick sort (randomize quick sort, iterative quick sort come from http://www.geeksforgeeks.org/iterative-quick-sort/): TLE
stooge sort : TLE
heap sort: 0.26s
radix sort: 0.22s
Only heap sort and radix sort can pass the test! and I think the radix sort is the fastest sorting algorithm since its complexity is O(n). However, as you said that on post #9, if use the std::sort and std::prior_queue in C++, it got the running time 0.11s!! Now the questions are :
1. If radix sort faster than the std::sort, then the std::prior_queue faster than my own prior_queue implementation MUCH MORE !? (say 0.22s vs 0.11s. I used binary heap to implement the prior_queue).
2. If the answer of question 1 is yes, then if I improve the efficiency of the prior_queue implementation, then the running time can be faster than 0.11s ? and even near your record 0.08 !? (I got some information from this article ftp://ftp.cs.utexas.edu/pub/techreports/honor_theses/cs-07-34-roche.pdf and according to it, some other heap implementations may be faster than binary heap).
[ 本帖最后由 assembly.jc 於 2013-11-20 03:54 AM 编辑 ]
作者: assembly.jc 发布时间: 2013-12-05
1. If radix sort faster than the std::sort, then the std::prior_queue faster than my own prior_queue implementation MUCH MORE !? (say 0.22s vs 0.11s. I used binary heap to implement the prior_queue).
My own experience is that using a binary heap with pre-allocation of memory runs faster than std::priority_queue, which must allocate memory dynamically.
作者: fitcat07 发布时间: 2013-12-05
http://en.wikipedia.org/wiki/Disjoint-set_data_structure
最后,佢哋睇埋我嘅方法后,认为都系同一方法。
我之前虽然已经识得 disjoint-set,但我无谂过可以用嚟解决呢个问题。原来我「重新发明车轮」,哈哈。
作者: fitcat07 发布时间: 2013-12-05
The easiest way to answer your question is to use std::sort in your code, submit it and then see the result.The time complexity of radix sort is O(dn) where d is the number of digits. Since max(n) is ...
Thanks for your suggestion and kindly reply


Yes, as you said that the radix sort has been improved by switching pointers instead of copy all elements between arrays before submit to SPOJ. Also, you're right, I make a mistake on previous post, I refers back to my text book (introduction to algorithms, MIT press) the time complexity of radix sort is O(d(2n+k)), i.e. O(d(2n + 10), but since the Ci > 1,000,000 can be reduced to 1,000,001 before sorting (because of the maximum of Mi is 1,000,000) , and so, the time complexity is O(7 (2n + 10)), i.e. O(14n) for sorting the mass of bag and jewellery, right?
Otherwise, Would I ask a stupid question? Does pre-allocation of memory mean using array instead of dynamic allocate memory using malloc() like functions? If it is true, actucally, I have used pre-allocation of memory method

作者: assembly.jc 发布时间: 2013-12-05
http://www.cplusplus.com/reference/algorithm/sort/
The example uses vector. It is very much like array except that it can grow dynamically. Yes, it requires the use of templates - but it is easy to use as you can see from the example. In fact, std::sort can apply to normal C arrays as well, e.g., std::sort(A, A+n) where n is the number of elements of A. If you need to sort in descending order, you can supply the third argument which is a binary function. Again, it is included in the example.
In this problem, you can modify all Ci > 1,000,000 to 1,000,001 before sorting. You're right and it helps to shorten the sorting time.
Since we know the maximum size, we can allocate a sufficient large memory for storing all elements in the heap. The allocated memory can be a fixed size array or it can be allocated using malloc() or similar functions. The point is that memory is not allocated each time an element is pushed into the help or memory has to be resized to cater for the growth. Since std::priority_queue does not support memory pre-allocation, the performance may not be as good as that of pre-allocated memory heaps.
作者: fitcat07 发布时间: 2013-12-05
If you don't use classes and templates, C++ is very similar to C (especially C99).To use std::sort, you don't need to use templates. See the link to an example below:
http://www.cplusplus.com/reference/algorithm/sort/
Yup, it is quite easy and convenient, although I don't know what it have been done, but it work! I have submitted the c++ version and here is the result:
std::sort + my priority_queue implementation : 0.24s (submitted 3 times got same running time)
std::sort + std::priority_queue: 0.24 - 0.26s (also submitted 3 times)
The results matches our time complexity analysis, the radix sort just faster than std::sort a little bit and since my priority_queue implementation use pre-allocated memory, it seems more stable than std::priority_queue when compare their running time.
However, the question still cannot be solved: even though use std::sort+ std::priority_queue, but why your solution is much faster?? say 0.11s! Do you use std::vector as well? Is it because I use unsigned long long to calculate the answer? Here is the code, Would you mind to point out the difference between that and your faster solution?
The core part: (I just modify your solution on post #9 a little bit, but it seems not the key factor of why it is slower than your original solution)
unsigned int m;
unsigned int v;
} Jewellery;
bool std_comp_bag(unsigned int i, unsigned int j) {
return(i<j);
}
bool std_comp_mass(Jewellery *i, Jewellery* j) {
return(i->m<j->m);
}
void lopov(std::vector<Jewellery*> &jv, std::vector<unsigned int> &cv) {
unsigned int i, j, n, k;
unsigned long long ans;
std::priority_queue<unsigned int> pq;
std::sort(cv.begin(), cv.end(), std_comp_bag);
std::sort(jv.begin(), jv.end(), std_comp_mass);
i=0; j=0; ans=0;
n = jv.size();
k = cv.size() - 1;
do {
while(i<n && jv->m<=cv[j]) pq.push(jv[i++]->v);
if(! pq.empty()) { ans += pq.top(); pq.pop(); }
} while(j++ < k);
printf("%llu\n", ans);
}
#define MAX_LINE_LEN 1024
void read_num(unsigned int *a) {
char s[MAX_LINE_LEN];
char *p;
fgets(s, MAX_LINE_LEN, stdin);
*a = 0;
p = s;
while(*p != '\n') *a = (*a * 10) + (*p++ - '0');
}
void read_pair(unsigned int *a, unsigned int *b) {
char s[MAX_LINE_LEN];
char *p;
fgets(s, MAX_LINE_LEN, stdin);
*a = 0;
p = s;
while(*p != ' ') *a = (*a * 10) + (*p++ - '0');
*b = 0;
p++;
while(*p != '\n') *b = (*b * 10) + (*p++ - '0');
}
int main() {
unsigned int i, c, n, k;
Jewellery j[MAX_DATA_LEN];
std::vector<unsigned int> cv;
std::vector<Jewellery*> jv;
read_pair(&n, &k);
for(i=0; i<n; i++) {
read_pair(&j[i].m, &j[i].v);
jv.push_back(&j[i]);
}
for(i=0; i<k; i++) {
read_num(&c);
cv.push_back(c);
}
lopov(jv, cv);
return(0);
}
作者: assembly.jc 发布时间: 2013-12-05

The difference in time may be due to the following issues in order of significance:
1. I use fread() to read most (or all depends on the input size) of the input. My buffer size is 64KB and the maximum input size is 6.9MB. So, the total number of calls to read input is at most about 100. Yours is at most 300,000. Calling system functions especially on input/output is time consuming.
2. Since we know the maximum size, we should call std::vector.reserve() to allocate sufficient memory to avoid dynamic memory allocation due to growth in size. Fyi, GCC reserve storage for 16 elements by default, IIRC. I use static array to store both bags and jewellery. Static array is usually faster than std::vector.
3. std::sort by default sorts the elements in ascending order. So, you don't need to pass in std_comp_bag. I don't know whether this affects performance or not. Since std::sort is highly optimized, I believe omitting the third argument will help though the difference may not be significant.
作者: fitcat07 发布时间: 2013-12-05
热门阅读
-
office 2019专业增强版最新2021版激活秘钥/序列号/激活码推荐 附激活工具
阅读:74
-
如何安装mysql8.0
阅读:31
-
Word快速设置标题样式步骤详解
阅读:28
-
20+道必知必会的Vue面试题(附答案解析)
阅读:37
-
HTML如何制作表单
阅读:22
-
百词斩可以改天数吗?当然可以,4个步骤轻松修改天数!
阅读:31
-
ET文件格式和XLS格式文件之间如何转化?
阅读:24
-
react和vue的区别及优缺点是什么
阅读:121
-
支付宝人脸识别如何关闭?
阅读:21
-
腾讯微云怎么修改照片或视频备份路径?
阅读:28