P5569 [SDOI2008] 石子合并 解题报告
题意
\(n\) 个石子排成一排,第 \(i\) 堆有 \(a_i\) 个,每次可以选取两个石子堆合并,得分是新石子堆的大小,求合并所有石子为一堆的最小得分。
\(n\leqslant 40000\)。
分析
大众科技,GarsiaWachs 算法。
设当前石子共 \(w\) 堆,分别为 \(v_{1\cdots w}\)。
- 找到最小的 \(k\) 满足 \(v_{k-1}<v_{k+1}\),然后找到最大的 \(p\) 满足 \(p<k\) 且 \(v_p>v_k+v_{k-1}\);
- 合并第 \(k,k+1\) 堆石子,并将其放在第 \(p\) 堆石子后面;
- 重复操作直到 \(w=1\)。
证明鸽了。
注意要在序列两端放置两个哨兵点。
复杂度:\(O(n^2)\),开 O2 能过,可以用平衡树优化到 \(O(n\log n)\)。
实际上有一个扩展,但是我还不会。
代码
#include<stdio.h>
#include<vector>
#define inf 2000000000
using namespace std;
const int maxn=40005;
int n,ans;
int a[maxn];
vector<int>v;
int main(){
scanf("%d",&n);
v.push_back(inf);
for(int i=1,x;i<=n;i++)
scanf("%d",&x),v.push_back(x);
v.push_back(inf);
for(int i=1;i<n;i++){
int k=0,p=0,rec=0;
for(int j=1;j<=n;j++)
if(v[j-1]<v[j+1]){
k=j;
break;
}
rec=v[k]+v[k-1],ans+=rec;
for(int j=k-1;j>=0;j--)
if(v[j]>rec){
p=j;
break;
}
v.erase(v.begin()+k),v.erase(v.begin()+(k-1)),v.insert(v.begin()+p+1,rec);
}
printf("%d\n",ans);
return 0;
}