难度
4
/
10
4/10
4/10
经典题
题目
放着
N
N
N 堆石子。每堆石子有
a
i
a_i
ai 个石头。
每次选择相邻的两堆石子
i
,
j
i\ ,\ j
i , j,把它合并成一堆新的,并获得
a
i
+
a
j
a_i+a_j
ai+aj分数。
计算出将该
N
N
N 堆石子合并成一堆的最小分数。
数据范围
N
≤
40000
N\le 40000
N≤40000
a
i
≤
200
a_i\le 200
ai≤200
思路
- 数据范围之大,DP显然不行。
- 我们使用 GarsiaWachs 算法(证明略)
- 假设第一堆左边有第零堆,第 N N N 堆右边有第 N + 1 N+1 N+1 堆,他们的石子数都是 ∞ \infin ∞,便于计算。
- 每次从左往右遍历 i i i, 找到第一个 a i − 1 < a i + 1 a_{i-1} < a_{i+1} ai−1<ai+1 的位置
- 删除 a i − 1 、 a i a_{i-1}、a_{i} ai−1、ai ,记录分数 K = a i − 1 + a i K=a_{i-1}+a_i K=ai−1+ai
- 让 j j j 从 i − 2 i-2 i−2 向左遍历,找到第一个 a j > K a_j>K aj>K 的位置
- 在 j j j 之后插入 K K K,总得分增加 K K K
- 循环第 2 ∼ 5 2\sim5 2∼5步,直到找不到为止(即只剩下前后两堆 ∞ \infin ∞和中间一堆)。
- 输出总得分即可。
核心代码
时间复杂度 O ( N log N ) O(N\log N) O(NlogN) ,可能会TLE,需要使用快读和O2优化等。
/*
_ __ __ _ _
| | \ \ / / | | (_)
| |__ _ _ \ V /__ _ _ __ | | ___ _
| '_ \| | | | \ // _` | '_ \| | / _ \ |
| |_) | |_| | | | (_| | | | | |___| __/ |
|_.__/ \__, | \_/\__,_|_| |_\_____/\___|_|
__/ |
|___/
*/
const int INF = 0x3f3f3f3f;
vector<int>V;
int work(){
int id;
int val;
for(int i = 1;i < V.size() - 1;++i){
if(V[i+1] > V[i-1]){
val = V[i] + V[i-1];
id = i-1;
break;
}
}
V.erase(V.begin() + id);
V.erase(V.begin() + id);
int id2;
for(int i = id-1;i >= 0;--i){
if(V[i] > val){
id2 = i;
break;
}
}
V.insert(V.begin() + id2 + 1,val);
return val;
}
int main()
{
int n;n = read();
V.push_back(INF);
for(int i = 1;i <= n;++i){
int t = read();
V.push_back(t);
}
V.push_back(INF);
ll ans = 0;
for(int i = 1;i < n;++i){
ans += work();
}
cout << ans;
return 0;
}