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;
}

浙公网安备 33010602011771号