本文章会探讨线性石子合并问题及其复杂度优化问题,环形石子合并问题,会持续更新。
咕咕咕~
线性石子合并
题目链接 (NOI1995) https://www.acwing.com/problem/content/284/
题意
设有堆石子排成一排,其编号为
。
每堆石子有一定的质量,可以用一个整数来描述,现在要将这堆石子合并成为一堆。
每次只能合并相邻的两堆,合并的代价为这两堆石子的质量之和,合并后与这两堆石子相邻的石子将和新堆相邻,合并时由于选择的顺序不同,合并的总代价也不相同。
例如有4堆石子分别为 1 3 5 2, 我们可以先合并1、2堆,代价为4,得到4 5 2, 又合并 1,2堆,代价为9,得到9 2 ,再合并得到11,总代价为4+9+11=24;
如果第二步是先合并2,3堆,则代价为7,得到4 7,最后一次合并代价为11,总代价为4+7+11=22。
问题是:找出一种合理的方法,使总的代价最小,输出最小代价。
输入格式
第一行一个数N表示石子的堆数。
第二行个数,表示每堆石子的质量(均不超过1000)。
输出格式
输出一个整数,表示最小代价。
数据范围
题解
f[l][r]表示把最初的第l堆到第r堆石子合并成一堆,需要消耗的最少体力。
,其他为
简单附代码:
#include <bits/stdc++.h>
#define Pair pair<int,int>
#define fir first
#define sec second
namespace fastIO{
#define BUF_SIZE 100000
#define OUT_SIZE 100000
//fread->read
bool IOerror=0;
//inline char nc(){char ch=getchar();if(ch==-1)IOerror=1;return ch;}
inline char nc(){
static char buf[BUF_SIZE],*p1=buf+BUF_SIZE,*pend=buf+BUF_SIZE;
if(p1==pend){
p1=buf;pend=buf+fread(buf,1,BUF_SIZE,stdin);
if(pend==p1){IOerror=1;return -1;}
}
return *p1++;
}
inline bool blank(char ch){return ch==' '||ch=='\n'||ch=='\r'||ch=='\t';}
template<class T> inline bool read(T &x){
bool sign=0;char ch=nc();x=0;
for(;blank(ch);ch=nc());
if(IOerror)return false;
if(ch=='-')sign=1,ch=nc();
for(;ch>='0'&&ch<='9';ch=nc())x=x*10+ch-'0';
if(sign)x=-x;
return true;
}
inline bool read(double &x){
bool sign=0;char ch=nc();x=0;
for(;blank(ch);ch=nc());
if(IOerror)return false;
if(ch=='-')sign=1,ch=nc();
for(;ch>='0'&&ch<='9';ch=nc())x=x*10+ch-'0';
if(ch=='.'){double tmp=1; ch=nc();for(;ch>='0'&&ch<='9';ch=nc())tmp/=10.0,x+=tmp*(ch-'0');}
if(sign)x=-x;return true;
}
inline bool read(char *s){
char ch=nc();
for(;blank(ch);ch=nc());
if(IOerror)return false;
for(;!blank(ch)&&!IOerror;ch=nc())*s++=ch;
*s=0;
return true;
}
inline bool read(char &c){
for(c=nc();blank(c);c=nc());
if(IOerror){c=-1;return false;}
return true;
}
template<class T,class... U>bool read(T& h,U&... t){return read(h)&&read(t...);}
#undef OUT_SIZE
#undef BUF_SIZE
};using namespace fastIO;using namespace std;
const int N=300+5;
const double eps=1e-7;
const double pi=acos(-1.0);
const int mod=998244353;
const int inf=0x3f3f3f3f;
int f[N][N];
int A[N],sum[N];
signed main(){
int n;read(n);
memset(f,0x3f,sizeof f);
for(int i=1;i<=n;i++){
read(A[i]);
sum[i]=sum[i-1]+A[i];
f[i][i]=0;
}
for(int len=1;len<=n-1;len++){
for(int l=1;l+len<=n;l++){
int r=l+len;
for(int k=l;k<r;k++)
f[l][r]=min(f[l][r],f[l][k]+f[k+1][r]);
f[l][r]+=sum[r]-sum[l-1];
}
}
printf("%lld\n",f[1][n]);
return 0;
}
这个算法的复杂度为,只能用来处理N<250的规模,这个问题符合”平行四边形优化“的原理,什么是平行四边形优化,本篇文章暂且放下,有兴趣可以自行百度,或者关注博主,会持续更新算法竞赛进阶指南的题解。
我们继续说怎么优化,
可以用表示区间的最优分割点,这样第三重循环可以缩小范围,
经过优化以后复杂度接近,可以处理n<3000的问题。
简单附代码:
#include <bits/stdc++.h>
#define Pair pair<int,int>
#define fir first
#define sec second
namespace fastIO{
#define BUF_SIZE 100000
#define OUT_SIZE 100000
//fread->read
bool IOerror=0;
//inline char nc(){char ch=getchar();if(ch==-1)IOerror=1;return ch;}
inline char nc(){
static char buf[BUF_SIZE],*p1=buf+BUF_SIZE,*pend=buf+BUF_SIZE;
if(p1==pend){
p1=buf;pend=buf+fread(buf,1,BUF_SIZE,stdin);
if(pend==p1){IOerror=1;return -1;}
}
return *p1++;
}
inline bool blank(char ch){return ch==' '||ch=='\n'||ch=='\r'||ch=='\t';}
template<class T> inline bool read(T &x){
bool sign=0;char ch=nc();x=0;
for(;blank(ch);ch=nc());
if(IOerror)return false;
if(ch=='-')sign=1,ch=nc();
for(;ch>='0'&&ch<='9';ch=nc())x=x*10+ch-'0';
if(sign)x=-x;
return true;
}
inline bool read(double &x){
bool sign=0;char ch=nc();x=0;
for(;blank(ch);ch=nc());
if(IOerror)return false;
if(ch=='-')sign=1,ch=nc();
for(;ch>='0'&&ch<='9';ch=nc())x=x*10+ch-'0';
if(ch=='.'){double tmp=1; ch=nc();for(;ch>='0'&&ch<='9';ch=nc())tmp/=10.0,x+=tmp*(ch-'0');}
if(sign)x=-x;return true;
}
inline bool read(char *s){
char ch=nc();
for(;blank(ch);ch=nc());
if(IOerror)return false;
for(;!blank(ch)&&!IOerror;ch=nc())*s++=ch;
*s=0;
return true;
}
inline bool read(char &c){
for(c=nc();blank(c);c=nc());
if(IOerror){c=-1;return false;}
return true;
}
template<class T,class... U>bool read(T& h,U&... t){return read(h)&&read(t...);}
#undef OUT_SIZE
#undef BUF_SIZE
};using namespace fastIO;using namespace std;
const int N=300+5;
const double eps=1e-7;
const double pi=acos(-1.0);
const int mod=998244353;
const int inf=0x3f3f3f3f;
int f[N][N];
int A[N],sum[N];
int s[N][N];
signed main(){
int n;read(n);
memset(f,0x3f,sizeof f);
for(int i=1;i<=n;i++){
read(A[i]);
sum[i]=sum[i-1]+A[i];
f[i][i]=0;
s[i][i]=i;
}
for(int len=1;len<=n-1;len++){
for(int l=1;l+len<=n;l++){
int r=l+len;
for(int k=s[l][r-1];k<=s[l+1][r];k++){
if(f[l][r]>f[l][k]+f[k+1][r]){
f[l][r]=f[l][k]+f[k+1][r];
s[l][r]=k;
}
}
f[l][r]+=sum[r]-sum[l-1];
}
}
printf("%lld\n",f[1][n]);
return 0;
}
但是当再大时,这个算法就不能承受,
我们还可以进行优化,
此时这种算法不属于动态规划的范畴了,
这个题在POJ1738,这个题把给到了
,而且是多组输入,最致命的是二维数组开不下这么多空间,时限是
,
,
,是可以接受的。
先放上北大爷lyd的代码,
//Author:XuHt
#include <cstdio>
#include <iostream>
using namespace std;
const int N = 50006;
int n, a[N], t, p, ans;
void work(int x) {
int k = a[x] + a[x-1];
ans += k;
for (int i = x; i < t - 1; i++) a[i] = a[i+1];
--t;
for (p = x - 1; p && a[p-1] < k; p--) a[p] = a[p-1];
a[p] = k;
while (p >= 2 && a[p] >= a[p-2]) {
int d = t - p;
work(p - 1);
p = t - d;
}
}
void An_old_Stone_Game() {
for (int i = 0; i < n; i++) scanf("%d", &a[i]);
t = 1;
ans = 0;
for (int i = 1; i < n; i++) {
a[t++] = a[i];
while (t >= 3 && a[t-3] <= a[t-1]) work(t - 2);
}
while (t > 1) work(t - 1);
cout << ans << endl;
}
int main() {
while (cin >> n && n) An_old_Stone_Game();
return 0;
}