A
B

P5569 / POJ 1738 一个古老的石头游戏An old Stone Game (GarsiaWachs算法)题解

一个古老的石头游戏An old Stone Game

洛谷

本题为石子合并super加强版(紫题(或者说原题))。

题目背景相信大家都特别熟悉了,这里按下不表。

首先数据范围中N≤50000,从根本上卡死了区间二维DP。

于是这里引用一种专门供石子合并而开发的新算法————。

GarsiaWachs算法

有关证明请移步OI.wiki

这里阐明做法:

1.寻找最小 \(k\),使 \(a_{k-1} \leq a_{k+1}\)

2.将 \(a_{k-1}\)\(a_k\) 合并,插入到从 \(k\) 往前数第一个比 \(a_k + a_{k-1}\) 大的数的右侧。

3.将数列中 \(a_{k-1}\)\(a_k\) 删除。

可以这样理解:

假设有三堆石子 \(a,b,c\)

case1:先合并 \(a,b\),则总得分为 \(2a+2b+c\)

case2:先合并 \(b,c\),则总得分为 \(a+2b+2c\)

显然当 \(a \leq c\) 时,先合并 \(a,b\) 更优。

代码实现

考虑做法中有数据的插入与删除,以及那可怕的数据范围,需要一种插入,删除,查询都很优秀的数据结构。

这里选用 vector 或链表。

\(O(n^2)\) 的代码放下:

#include<bits/stdc++.h>
#define Blue_Archive return 0
#define int long long
using namespace std;
const int N=5e4;
const int INF = 0x7f7f7f7f7f7f7f7f;

int n;
int num[N];
int ans;

vector<int> op;

inline int Shiroko()
{
	int k = op.size() - 1 - 1;
    for(int i = 0;i < k;i ++)
    {
        if(op[i] <= op[i + 2])
        {
            k = i;
            break;
        }
    }
    int tmp = op[k] + op[k + 1];
    op.erase(op.begin() + k);
    op.erase(op.begin() + k);
    int in = -1;
    for(int i = k - 1;i >= 0;i --)
    {
        if(op[i] > tmp)
        {
            in = i;
            break;
        }
    }
    op.insert(op.begin() + in + 1,tmp);
    return tmp;
}

signed main()
{
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
	while(cin >> n)
    {
        if(n == 0) Blue_Archive;
        for(int i = 1,x;i <= n;i ++)
        {
            cin >> x;
            op.push_back(x);
        }
        for(int i = 1;i < n;i ++)
        {
            ans += Shiroko();
        }
        cout << ans << "\n";
    }
	Blue_Archive;
}

然鹅

显然这份 \(n^2\) 代码没有达到原算法 \(O(n \log n)\) 的优秀时间复杂度。

于是我们考虑优化。

参考题解:巨犇

运用dfs进行优化至 \(O(n \log n)\)

下面附上 \(O(n \log n)\) 代码

// 一个古老的石头游戏 An old Stone Game
#include<bits/stdc++.h>
#define Blue_Archive return 0
#define int long long
using namespace std;
const int N=5e4;
const int INF = 0x7f7f7f7f7f7f7f7f;

int n;
int t;
int ans;
int st[N];

inline void Shiroko(int k)
{
    int tmp = st[k] + st[k - 1];
    ans += tmp;
    for(int i = k;i < t - 1;i ++) st[i] = st[i + 1];
    t --;
    int j = 0;
    for(j = k - 1;j > 0 && st[j - 1] < tmp;j --) st[j] = st[j - 1];
    st[j] = tmp;
    while(j >= 2 && st[j] >= st[j - 2])
    {
        int d = t - j;
        Shiroko(j - 1);
        j = t - d;
    }
}

signed main()
{
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
	while(cin >> n)
    {
        if(n == 0) Blue_Archive;
        for(int i = 0;i < n;i ++)
        {
            cin >> st[i];
        }
        t = 1;
        ans = 0;
        for(int i = 1; i < n;i ++)
        {
            st[t ++] = st[i];
            while(t >= 3 && st[t - 3] <= st[t - 1]) Shiroko(t - 2);
        }
        while(t > 1) Shiroko(t - 1);
        cout << ans << "\n";
    }
	Blue_Archive;
}

posted @ 2025-07-31 07:02  MyShiroko  阅读(32)  评论(0)    收藏  举报