P5569 [SDOI2008] 石子合并 解题报告

P5569 [SDOI2008] 石子合并 解题报告

在刷sdoi2008的时候偶然看见的,第一眼似曾相识,第二眼这不石子合并(弱化版)吗?又不小心一眼看到数据范围,这不对吧,然后就放弃了点开了题解,恍然大悟,其实根本没悟,知道了 Garsia–Wachs 算法 这么个算法

1. 原题 P5569 [SDOI2008] 石子合并

2. 概述题意

给你n堆石子,每次只能选择相邻的两堆合并,记两堆石子的和为分数,求最小分数

3. 算法介绍

如果有能看懂oi-wiki上解释的巨佬可以当我这个蒟蒻什么也没说,因为我看不懂,但是知道结论

首先这是个贪心,思路就是假设有 \(a, b, c\) 三个数,如果合并前两堆那么分数就是 \(2a, 2b, c\) 如果合并后两堆,那么分数 \(a, 2b, 2c\),所以分数只在于 \(a\)\(c\)

假设现在有一个数组 \(num[0...n+1]\) ,首先令 \(num[0] = num[n + 1] = \infty\) 每次我们寻找一个最小的 \(k\) 使得 \(num[k - 1] <= num[k + 1]\),那么我们将 \(num[k - 1]\)\(num[k]\) 合并,之后再从 \(k\)\(1\) 寻找第一个 \(j\) 满足 \(num[j] > num[k] + num[k - 1]\),然后再把合并过的值,插入再 \(num[j]\) 之后

我们来模拟一下:

假设 \(num[6] = {\infty,7, 6, 7, 10, 4, 2,\infty}\)

第一次:因为 \(6 < 10\) 那么 \(k = 2\),合并后得到 \(13\) ,向前找 最后放在 \(num[0] = \infty\) 之后得到 \(num[5] = {\infty,13, 7, 10, 4, 2,\infty}\)

第二次:因为 \(4 < \infty\) 那么 \(k = 5\),合并后得到 \(8\) ,向前找 最后放在 \(num[3] = 10\) 之后得到 \(num[4] = {\infty,13, 7, 10, 8,\infty}\)

第三次:\(num[3] = {\infty,15 ,13, 10,\infty}\)

第四次:\(num[2] = {\infty,28, 10,\infty}\)

最后一次:\(num[3] = {\infty,38,\infty}\)

得到最后的解为:102

4. 参考代码

本题输入规模较大,不开读写优化会T,所以不管是快读快写还是关流同步,反正写上就是了

#include<bits/stdc++.h>
#define int long long

using namespace std;

typedef long long LL;
typedef pair<int, int> PII;
const int N = 1e6 + 10;

int n;
vector<int> v;

int merge(){
    int k = 1, j;
    for(; k <= n ; k ++) if(v[k - 1] < v[k + 1]) break;
    int sum = v[k] + v[k - 1];
    for(j = k - 1 ; j >= 0 ; j --) if(v[j] > v[k] + v[k - 1]) break;
    v.erase(v.begin() + k - 1), v.erase(v.begin() + k - 1);
    v.insert(v.begin() + j + 1, sum);
    return sum;
}

signed main(){
    
    ios::sync_with_stdio(false);
    cin.tie(0);

    cin>>n;

    v.push_back(1e9);
    for(int i = 1 ; i <= n ; i ++){
        int a;
        cin>>a;
        v.push_back(a);
    }
    v.push_back(1e9);

    int ans = 0;
    for(int i = n - 1 ; i >= 1 ; i --){
        ans += merge();
    }
    cout<<ans;

    return 0;
}
posted @ 2026-04-21 19:37  神烦doge  阅读(4)  评论(0)    收藏  举报