石子合并问题总结

直线石子合并

题目链接

时间复杂度: O ( n 3 )
空间复杂度: O ( n 2 )

k [ l , r ) , d p [ l ] [ r ] = min d p [ l ] [ k ] + d p [ k + 1 ] [ r ] + i = l r a [ i ]
, 为合并区间 [ l , r ] 的最小合并代价。

代码:

#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int maxn = 1e3+7;
int dp[maxn][maxn];
int sum[maxn];
int a[maxn];
int inf = 0x3f3f3f3f;
int d(int l,int r){
    if(dp[l][r] != inf) return dp[l][r];
    for(int k = l;k<r;k++){
        dp[l][r] = min(dp[l][r],d(l,k)+d(k+1,r)+sum[r]-sum[l-1]);
    }
    return dp[l][r];
}
int main(){
    ios::sync_with_stdio(0);
    int n;cin >> n;
    for(int i = 1;i<=n;i++){
        cin >> a[i];
        sum[i]+=a[i]+sum[i-1];
    }
    memset(dp,0x3f,sizeof(dp));
    for(int i = 0;i<=n;i++)
        dp[i][i] = 0;
    cout << d(1,n) << endl;
}

圆形石子合并

n   10 2

题目链接
注意:此题是多组输入。

解法1

时间复杂度: O ( n 3 )
空间复杂度: O ( n 2 )

dp方程为

k [ l , r ) ,   d p [ l ] [ r ] = d p [ l ] [ k ] + i = l r a [ i ] ,   when  l r
以及
k [ 1 , r ) [ l , n ] ,   d p [ l ] [ r ] + i = l n a [ i ] + i = 1 r a [ i ] ,   when l>r

初始条件:

d p [ i ] [ j ] = 0 , when  i = j , d p [ i ] [ j ] = , otherwise.

递归

#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int maxn = 1e2+7;
int dp[maxn][maxn];
int sum[maxn];
int a[maxn];
int inf = 0x3f3f3f3f;
int n;
int d(int l,int r){
    if(l*r == 0) return inf;
    if(dp[l][r] < inf) return dp[l][r];
    if(l<=r)
        for(int k = l;k<r;k++){
            dp[l][r] = min(dp[l][r],d(l,k)+d(k+1,r)+sum[r]-sum[l-1]);
        }
    else{
        for(int k = l;k<r || k>=l;k=k%n+1){
            dp[l][r] = min(dp[l][r],d(l,k)+d((k)%(n)+1,r)+sum[n]-sum[l-1]+sum[r]);
        }
    }
    return dp[l][r];
}
int main(){
    ios::sync_with_stdio(0);
    while(cin >> n){
        memset(dp,0x3f,sizeof(dp));
        memset(sum,0,sizeof(sum));
        for(int i = 0;i<=n;i++)
            dp[i][i] = 0;
        for(int i = 1;i<=n;i++){
            cin >> a[i];
            sum[i]=a[i]+sum[i-1];
        }
        if(n == 1) {
            cout << a[1] << endl;
            continue;
        }
        int ans = inf;
        for(int i = 1;i<=n;i++)
            ans = min(ans,d(i,(i+n-1)%n));
        cout << ans << endl;
    }
}

解法2

将石子堆复制一份,用直线情形解决。

注意数组大小。

迭代

#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int maxn = 2e2+7;
int dp[maxn][maxn], sum[maxn];
int n;
int main(){
    while(~scanf("%d",&n)){
        memset(dp,0x1f,sizeof(dp));
        for(int i = 0;i<=2*n;i++){
            dp[i][i] = 0;
        }
        for(int i = 1;i<=n;i++){
            scanf("%d",&sum[i]);
            sum[i+n] = sum[i];
        }
        for(int i = 1;i<=2*n;i++){
            sum[i]+=sum[i-1];
        }
        for(int d = 1;d<n;d++){
            for(int i = 1;i<=2*n-d;i++){
                int j = i+d;
                for(int k = i;k<j;k++){
                    dp[i][j] = min(dp[i][k]+dp[k+1][j]+sum[j]-sum[i-1],dp[i][j]);
                }
            }
        }
        int ans = 0x1f1f1f1f;
        for(int i = 1;i<=n;i++){
            int j = i + n - 1;
            ans = min(ans,dp[i][j]);
        }
        printf("%d\n",ans);
    }
}

递归

这里略去……因为差不多。

四边形不等式优化

时间复杂度: O ( n 2 )
空间复杂度同

直线石子合并

迭代

鸽了

环形石子合并

题目链接

n 10 3

#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int maxn = 2e3+7;
int dp[maxn][maxn], sum[maxn], s[maxn][maxn];
int n;
int main(){
    scanf("%d",&n);
    memset(dp,0x1f,sizeof(dp));
    for(int i = 0;i<=2*n;i++){
        dp[i][i] = 0;
        s[i][i] = i;
    }
    for(int i = 1;i<=n;i++){
        scanf("%d",&sum[i]);
        sum[i+n] = sum[i];
    }
    for(int i = 1;i<=2*n;i++){
        sum[i]+=sum[i-1];
    }
    for(int d = 1;d<n;d++){
        for(int i = 1;i<=2*n-d;i++){
            int j = i+d;
            int &temp = dp[i][j];
            int &ts = s[i][j];
            for(int k = s[i][j-1];k<=s[i+1][j];k++){
                int orz = dp[i][k]+dp[k+1][j]+sum[j]-sum[i-1];
                if(temp > orz){
                    temp = orz;
                    ts = k;
                }
            }
        }
    }
    int ans = 0x1f1f1f1f;
    for(int i = 1;i<=n;i++){
        int j = i + n - 1;
        ans = min(ans,dp[i][j]);
    }
    printf("%d\n",ans);
}

GarsiaWachs算法

题目链接

n 5 × 10 4

时间复杂度: O ( n 2 ) ,若使用平衡树维护 O ( n log n )
空间复杂度: O ( n )

因为这个算法不是那么直观,所以我多写一点。

从朴素贪心开始

考虑三块连续的石堆 [ a , b , c ] ,计算两种合并方法的代价。

s 1 = ( a + b ) + ( ( a + b ) + c ) = 2 a + 2 b + c s 2 = ( a + ( b + c ) ) + ( b + c ) = a + 2 b + 2 c

注意到,若 a c ,就有 s 1 s 2 。所以,三个连续石堆的合并方法,只与 a c 的大小关系有关。

所以我们就能得到一个朴素的贪心算法,即:
不断地从左往右遍历,找到形似 [ a , b , c ] a c 的三个连续石堆,就合并 a b

但是朴素贪心算法是错误的。
反例: [ 6 , 4 , 2 , 1 , 3 , 5 , 7 ] 。最小代价是74,贪心结果是76。

形式证明鸽了。

Garisia-Wachs的改进

  • 设石堆数组为 s t o n e ,下标从0开始。其中每个元素代表此石堆的石子数量。
  • 约定 s t o n e [ 0 ] = s t o n e [ l e n ] = + , a n s = 0
  • 此数组长度记为 | s t o n e | = l e n ,计入两端。也就是说 [ + , 1 , + ] 长度为3。同时,因为至少有一个石堆,3也是 s t o n e 数组的最短可能长度。
  • 数组的下标较小的一端为左端。

具体算法如下:
1. 依次令 i = 1 l e n 2 ,直到有 s t o n e [ i ] s t o n e [ i + 2 ] 。(肯定会出现的,想想为啥)
2. 令 t e m p = s t o n e [ i ] + s t o n e [ i + 1 ]
3. 向左寻找,找到 j 满足 j < i s t o n e [ j ] > t e m p
4.删除 s t o n e [ i + 1 ] 这个元素(右方元素依次左移),并将 t e m p 插入到 s t o n e [ j ] 的右边(右方元素依次右移)。本次合并代价为 t e m p ,累加入 a n s
5.反复执行此过程,直到 s t o n e 数组内只有一个非无穷元素(只剩一个石堆 ),此时 a n s 的值即是最小合并代价。

正确性证明……鸽了。

vector朴素实现

用stl的vector实现。用时接近2s。
注意答案用long long。

#include<bits/stdc++.h>
#define LL long long
#define sz(v) int(v.size())
using namespace std;
const int maxn = 5e4+7;
const LL inf = 0x3f3f3f3f3f3f3f3f;
int n,x;
int main(){
    scanf("%d",&n);
    vector<LL> v;
    v.push_back(inf);
    for(int i = 1;i<=n;i++){
        scanf("%d",&x);
        v.push_back(x);
    }
    v.push_back(inf);

    LL ans = 0;
    for(int i = 1;i+2<sz(v);i++){
        if(v[i]<=v[i+2]){
            v[i] += v[i+1];
            LL temp = v[i];
            ans += temp;
            v.erase(v.begin()+i+1); //删除v[i+1]这个元素
            for(int j = i;j>=1;j--){
                if(v[j-1]>temp){ //往回找到合适的位置
                    v.erase(v.begin()+i);//删除v[i]这个元素
                    v.insert(v.begin()+j,temp);//插入v[i]到v[j]之后
                    break;
                }
            }
            i = 0;
        }
    }
    printf("%lld\n",ans);
}

值得注意的是,vector的insert方法在传入的位置之前(左方)插入元素。

有点厉害的实现

265ms……很强
51nod上扒的。

#include <stdio.h>
#include <algorithm>
#include <iostream>
#include <string.h>
#define MAXN 50005
#define LL long long
using namespace std;
int n, num;
LL ans;
int data[MAXN];
void dfs(int now)
{
    int j;
    int temp = data[now - 1] + data[now];//代价
    ans += (LL)temp;
    for(int i = now; i < num - 1; i++) data[i] = data[i + 1];
    num--;
    for(j = now - 1; j > 0 && data[j - 1] < temp; j--) data[j] = data[j - 1];
    data[j] = temp;
    while(j >= 2 && data[j - 2] <= data[j])
    {
        int d = num - j;
        dfs(j - 1);
        j = num - d;
    }
}
int main()
{
    scanf("%d", &n);
    for(int i = 0; i < n; i++) scanf("%d", &data[i]);
    num = 1;
    ans = 0;
    for(int i = 1; i < n; i++)
    {
        data[num++] = data[i];
        while(num>=3 && data[num-3]<=data[num-1]) dfs(num - 2);
    }
    while(num > 1) dfs(num - 1);
    printf("%lld\n", ans);

    return 0;
}

猜你喜欢

转载自blog.csdn.net/lanadeus/article/details/79348526
今日推荐