• [洛谷]P1880 石子合并问题


    Problem

    portal: P1880 石子合并

    Description

    在一个圆形操场的四周摆放N堆石子,现要将石子有次序地合并成一堆.规定每次只能选相邻的2堆合并成新的一堆,并将新的一堆的石子数,记为该次合并的得分。

    试设计出1个算法,计算出将N堆石子合并成1堆的最小得分和最大得分.

    Input

    数据的第1行是正整数N,1≤N≤100,表示有N堆石子.第2行有N个数,分别表示每堆石子的个数.

    Output

    输出共2行,第1行为最小得分,第2行为最大得分.

    Sample

    Sample Input

    4
    4 5 9 4
    

    Sample Output

    43
    54
    

    Solution

    Analysis

    设max_score(start, end)是起点为第start堆石子,终点为第end堆石子的最高得分。
    对于这部分石子堆,存在第mid堆石子,使得最后一步的合并为start至mid这部分石子合并的一个石子堆mid+1至end这部分石子合并的一个石子堆的合并,并且左半部分的得分是最高的,右半部分的得分也是最高的。

    得到公式
    max_score(start,end) = max(max_score(start, mid) + max_score(mid + 1) + total(start, end))   (start != end)
    左半部分的最高得分+右半部分的最高得分+最终合并的得分 mid的取值为start一直到end的前一个 (可能存在5->6->1->2->3->4这种情况, 此时mid取5, 6, 1, 2, 3)
    max_score(start,end) = 0 (start == end)

    根据跨度逐渐增加,最后计算出这些石子堆的最高得分。
    最低得分类似,不过每次求最小的。

    细节见代码

    Code

    #include <bits/stdc++.h>
    #define MAX_N 105
    
    using namespace std;
    
    int n, cnt[MAX_N];
    // 最大得分计算所需要的中间值存储
    int max_score[MAX_N][MAX_N];
    // 最小得分计算所需要的中间值存储
    int min_score[MAX_N][MAX_N];
    // 统计从i到j堆石子的总数, i可能会小于j, 此时从i计算到n, 再从1计算到j
    int total[MAX_N][MAX_N];
    
    // 当前堆的下一个堆的序号
    int next(int x) { 
        if (x == n) return 1;
        else return x + 1;
    }
    
    int main(void) {
        cin >> n;
        for (int i = 1; i <= n; i++) {
            cin >> cnt[i];
        }
    
     	// 两个for循环, 完善所有起点为第i堆, 终点为第j堆的石子的总数
        for (int i = 1; i<= n; i++) {
        	total[i][i] = cnt[i];
        }
        for (int range = 1; range < n; range++) {
        	for (int i = 1; i <= n; i++) {
        		int j = i + range;
        		if (j > n) j -= n;
    			total[i][j] = total[next(i)][j] + cnt[i];
        	}
        }
        
        // 一个石子堆不用合并, 结果为0
        for (int i = 1; i <= n; i++) {
        	max_score[i][i] = 0;
        	min_score[i][i] = 0;
        }
    
        int start, end;
        int max_result = 0, min_result = INT_MAX;
        // 跨度逐渐增加, 从起点与后面的第一个石子堆合并, 到起点与后面的n-1个, 即所有的石子堆合并
        for (int range = 1; range < n; range++) {
        	int max_value, min_value;
        	// 由于是环形操场, 所以起点不是固定的
        	for (start = 1; start <= n; start++) {
        		end = start + range;
        		if (end > n) end -= n; // 环形操场
    
        		max_value = 0;
        		min_value = INT_MAX;
        		for (int mid = start; mid != end; mid = next(mid)) {
        			// 使用公式
        			max_value = max(max_value, max_score[start][mid] + max_score[next(mid)][end] + total[start][end]);
        			min_value = min(min_value, min_score[start][mid] + min_score[next(mid)][end] + total[start][end]);
        		}
        		// 存储起点到终点这部分的 最大/最小 值
        		max_score[start][end] = max_value;
        		min_score[start][end] = min_value;
    
        		// 等所有的石子堆都参与时, 找出起点不同时最大/最小得分的值
        		if (range == n - 1) {
    	    		max_result = max(max_result, max_score[start][end]);
    	    		min_result = min(min_result, min_score[start][end]);
        		}
        	}
        }
        cout << min_result << endl;
        cout << max_result << endl;
    }
    
  • 相关阅读:
    【bzoj 1143】[CTSC2008]祭祀river
    【SRM-09 B】撕书II
    【刷题记录】BZOJ-USACO
    【noip 2014】提高组Day2T3.华容道
    【noip 2012】提高组Day2T3.疫情控制
    【noip 2012】提高组Day1T3.开车旅行
    点分治
    2—SAT问题
    生成树
    莫比乌斯反演
  • 原文地址:https://www.cnblogs.com/by-sknight/p/11748771.html
Copyright © 2020-2023  润新知