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;
}