【GarsiaWachs算法模板】HYSBZ - 3229 M - 石子合并

M - 石子合并  HYSBZ - 3229

  在一个操场上摆放着一排 N 堆石子。现要将石子有次序地合并成一堆。规定每次只能选相邻的 2 堆石子合并成新的一堆,并将新的一堆石子数记为该次合并的得分。

  试设计一个算法,计算出将 N 堆石子合并成一堆的最小得分。

 

Input

  第一行是一个数 N 。

  以下 N 行每行一个数 A ,表示石子数目。

 

Output

  共一个数,即N堆石子合并成一堆的最小得分。

 

Sample Input

4 1 1 1 1

Sample Output

8

Hint

 

对于 100% 的数据,1≤N≤40000

对于 100% 的数据,1≤A≤200

石子合并问题的专门算法GarsiaWachs算法: 
先从序列中找第一个st【k】使得st【k-1】<=st【k+1】然后合并st【k-1】与st【k】; 
再从序列中从k往前找第一个st【j】使得st【j】>st【k-1】+st【k】然后将这个合并后的放在j位置后; 
如此往复直到只剩一堆; 
此题暴力枚举即可,时间复杂度O(n^2)可以用平衡树去维护那个序列,实现O(nlogn)

 

#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++)
    {
        //printf("%d %d\n",num,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;
}
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int read()
{
    int x=0,f=1; char ch=getchar();
    while (ch<'0' || ch>'9') {if (ch=='-') f=-1; ch=getchar();}
    while (ch>='0' && ch<='9') {x=x*10+ch-'0'; ch=getchar();}
    return x*f;
}

int st[50000];
int n,t;
int ans=0;

void work(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;
            work(j-1);
            j=t-d;
        }
}

int main()
{
    n=read();
    for (int i=0; i<n; i++) st[i]=read();
    t=1;ans=0;
    for (int i=1; i<n; i++)
        {
            st[t++]=st[i];
            while (t>=3 && st[t-3]<=st[t-1])
                work(t-2);
        }
    while (t>1) work(t-1);
    printf("%d\n",ans);
    return 0;
}

 

如果N比较小的话可以直接用区间DP来做,参考CSU - 1592 石子归并

最优树算法:

 

思路:

石子合并问题实际上就是最优树问题

 

先从序列中找第一个st【k】使得st【k-1】<=st【k+1】然后合并st【k-1】与st【k】; 

再从序列中从k往前找第一个st【j】使得st【j】>st【k-1】+st【k】然后将这个合并后的数放在j位置后; 

如果找不到这样的j就把合并后的数放在最前面

如果找不到这样的k就合并最后2个数(因为这是最小的2个数)

如此往复直到只剩一堆;

这个算法的时间效率和快速排序差不多,最坏情况是n^2,但是一般情况下很快

代码:

#include<iostream>
#include<stdio.h>
using namespace std;
 
int main()
{
	int n, low = 1, list[40005];
	long long ans = 0;
	scanf("%d", &n);
	for (int i = 1; i <= n; i++)scanf("%d", &list[i]);
	while (low < n - 1)
	{
		int i;
		for (i = low; i < n - 1; i++)if (list[i] <= list[i + 2])
		{
			list[i + 1] += list[i], ans += list[i + 1];
			for (int j = i; j > low; j--)list[j] = list[j - 1];
			low++;
			int j = i + 1;
			while (list[j] > list[j - 1] && j > low)
			{
				list[j] ^= list[j - 1] ^= list[j] ^= list[j - 1];
				j--;
			}
			break;
		}
		if (i == n - 1)
		{
			list[n - 1] += list[n];
			ans += list[--n];
		}
	}
	if (low == n - 1)ans += list[n - 1] + list[n];
	cout << ans;
	return 0;
}

小范围n的区间DP

#include <bits/stdc++.h>
using namespace std;
const int inf=0x3f3f3f3f;
int dp[1005][1005];
int sum[1005];
int main()
{
    int n;
    while(~scanf("%d",&n))
    {
        memset(dp,inf,sizeof(dp));
        sum[0]=0;
        for(int i=1;i<=n;i++)
        {
            int m;
            scanf("%d",&m);
            sum[i]=sum[i-1]+m;
            dp[i][i]=0;
        }
        for(int len=2;len<=n;len++)
        {
            for(int i=1;i<=n;i++)
            {
                int j=i+len-1;
                if(j>n) break;
                for(int k=i;k<j;k++)
                {
                    dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]+sum[j]-sum[i-1]);
                }
            }
        }
        printf("%d\n",dp[1][n]);
    }
    return 0;
}

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值