poj 2442(堆的stl实现)

Sequence
Time Limit: 6000MS Memory Limit: 65536K
Total Submissions: 6281 Accepted: 1962

Description

Given m sequences, each contains n non-negative integer. Now we may select one number from each sequence to form a sequence with m integers. It's clear that we may get n ^ m this kind of sequences. Then we can calculate the sum of numbers in each sequence, and get n ^ m values. What we need is the smallest n sums. Could you help us?

Input

The first line is an integer T, which shows the number of test cases, and then T test cases follow. The first line of each case contains two integers m, n (0 < m <= 100, 0 < n <= 2000). The following m lines indicate the m sequence respectively. No integer in the sequence is greater than 10000.

Output

For each test case, print a line with the smallest n sums in increasing order, which is separated by a space.

Sample Input

1
2 3
1 2 3
2 2 3

Sample Output

3 3 4

Source

POJ Monthly,Guang Lin


首先要深入理解题意:

输入是m行*n列的矩阵。需要从每一行选一个数字,这样会构成m个数字的序列。

对这个序列求和,问最小的n个和是什么。


首先,要深刻理解,我们需要维护多少个数字。因为题目中只要求前n个最小和,我们只需要每步维护n个最小的数字即可。

比如第一行和第二行各挑一个数字求和,可以形成n*n个数字,排个序后,我们只需要保存前n个数字即可,根本不用管其余的数。第三行和上一步形成的这n个数字分别求和,又形成了n*n个数字,排个序后,再只保存前n个数字即可。因此可以逐步迭代计算。


如果按照上面的方法,写一个朴素的暴力算法,则空间复杂度为n*n,时间复杂度为(n*n + n*log(n)) * m 。可是TLE了。。

/*Source Code
Problem: 2442		User: 775700879
Memory: N/A		Time: N/A
Language: G++		Result: Time Limit Exceeded

    Source Code*/

    #include 
   
   
    
    
    #include 
    
    
     
     
    #include 
     
     
      
      
    #include 
      
      
       
       
    #include 
       
       
         #include 
        
          #include 
         
           #include 
          
            #include 
           
             #define oo 1000000 #define MAXV 500 * 2 using namespace std; vector 
            
              result; int temp[2000*2000+10]; int last[2010]; int line[2010]; int main() { int t; scanf("%d", &t); while (t--) { int n, m; scanf("%d%d", &m, &n); int i, j, k; for (i = 0; i < n; i++) { scanf("%d", last+i); } for (i = 1; i < m; i++) { for (j = 0; j < n; j++) { scanf("%d", line+j); } int count = 0; for (j = 0; j < n; j++) { for (k = 0; k < n; k++) { temp[count++] = last[j] + line[k]; } } sort(temp, temp+count); for (j = 0; j < n; j++) { last[j] = temp[j]; } } for (i = 0; i < n; i++) { printf("%d ", last[i]); } printf("\n"); } return 0; } 
             
            
           
          
         
       
      
      
     
     
    
    
   
   


需要优化。想到用堆。因为事实上我们每步都只需要维护最小的n个数即可。那我们就维护一个大跟堆(注意这里是大跟堆),这个 堆含有固定的n个元素(意味着初始化的时候,堆中有n个元素。以后只要插入一个元素,就要删除一个元素来保证堆中元素数目恒定为n)。
这个思路有了以后,再细想一下步骤:每一步先把thisline[0] 和 [lastheap[0], lastheap[n-1]] 分别相加,形成n个数字,建堆thisheap。再用[thisline[1]-thisline[n-1]]与[lastheap[0], lastheap[n-1]]分别求和,依次与thisheap的堆顶(最大值)比较,如果发现比最大值大,则舍弃。发现比最大值小,则删除堆中最大值,将新值push到堆中。

堆的几个重要的stl操作:
stl中的堆 都是 大根堆
make_heap(v.begin(), v.end()); 将n个元素的数组建堆
pop_heap(v.begin(), v.end()); 将n个元素的堆的堆顶pop掉。此时数组中的0-n-2是一个有 n-1个元素的新堆。被pop掉的 原堆顶元素被放到了n-1的位置。因此往往在这一步 之后还需要执行 v.pop_back();
push_heap(v.begin(), v.end()); 这一步首先要 确保数组中的0-n-2是一个有n-1个元素的堆,而 且新元素已经被放置在了v[n-1]位置。调用这个操作会将v[n-1] shiftup到合适的位置。因此在这一步之前要 先执行v.push_front(x);
这么做的话,ac需要2600ms+, 如果加一个小小的优化:将thisline先排序,当发现求和大于堆顶元素时,后面的就都不用比较了。这么优化可以400+ms AC。
代码如下:


/*Source Code
Problem: 2442		User: 775700879
Memory: 724K		Time: 407MS
Language: G++		Result: Accepted

    Source Code*/

    #include 
    
    
     
     
    #include 
     
     
      
      
    #include 
      
      
       
       
    #include 
       
       
        
        
    #include 
        
        
          #include 
         
           #include 
          
            #include 
           
             #include 
            
              #define oo 1000000 #define MAXV 500 * 2 using namespace std; vector 
             
               result; int temp[2000*2000+10]; int last[2010]; int cur[2010]; int line[2010]; int main() { int t; scanf("%d", &t); while (t--) { int n, m; scanf("%d%d", &m, &n); int i, j, k; for (i = 0; i < n; i++) { scanf("%d", last+i); } make_heap(last, last+n); for (i = 1; i < m; i++) { for (j = 0; j < n; j++) { scanf("%d", line+j); } sort(line, line+n); for (j = 0; j < n; j++) { cur[j] = last[j] + line[0]; } //保证先向堆中插入 n 个元素。 make_heap(cur, cur+n);//建堆 for (j = 0; j < n; j++) { for (k = 1; k < n; k++) { if (last[j] + line[k] > cur[0]) break; pop_heap(cur, cur+n); //将最大的元素pop掉 shiftdown cur[n-1] = last[j] + line[k]; push_heap(cur, cur+n);//将新的元素push到堆里 shiftup //在这个循环中,堆元素始终没有发生变化,一直是n } } for (j = 0; j < n; j++) { last[j] = cur[j]; } } sort(last, last + n); for (i = 0; i < n; i++) { printf("%d ", last[i]); } printf("\n"); } return 0; } 
              
             
            
           
          
        
       
       
      
      
     
     
    
    

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是 poj3714 突袭的代码实现,使用了 Kruskal 算法求解最小生成树: ```c++ #include <iostream> #include <algorithm> #include <vector> using namespace std; const int MAXN = 20005; const int MAXM = 100005; struct Edge { int u, v, w; bool operator<(const Edge& other) const { return w < other.w; } }; int n, m, p; int parent[MAXN], depth[MAXN]; Edge edges[MAXM]; void make_set(int v) { parent[v] = v; depth[v] = 0; } int find_set(int v) { if (v == parent[v]) { return v; } return parent[v] = find_set(parent[v]); } void union_sets(int a, int b) { a = find_set(a); b = find_set(b); if (a != b) { if (depth[a] < depth[b]) { swap(a, b); } parent[b] = a; if (depth[a] == depth[b]) { depth[a]++; } } } int kruskal() { int ans = 0; for (int i = 1; i <= n; i++) { make_set(i); } sort(edges, edges + m); for (int i = 0; i < m; i++) { int u = edges[i].u, v = edges[i].v, w = edges[i].w; if (find_set(u) != find_set(v)) { union_sets(u, v); ans += w; } } return ans; } int main() { while (cin >> n >> m >> p) { for (int i = 0; i < m; i++) { int u, v, w; cin >> u >> v >> w; edges[i] = {u, v, w}; } int ans1 = kruskal(); for (int i = 1; i <= n; i++) { make_set(i); } for (int i = 0; i < p; i++) { int u, v, w; cin >> u >> v >> w; edges[i] = {u, v, -w}; } int ans2 = kruskal(); cout << ans2 - ans1 << endl; } return 0; } ``` 在这个实现中,使用了一个 `Edge` 结构体来表示一条边,包括起点、终点和边权。然后使用 Kruskal 算法求解最小生成树,分别计算突袭前和突袭后的最小生成树的权值和,最终答案为突袭后的权值和减去突袭前的权值和。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值