石子合并-直线版

(点击此处查看题目)

朴素写法

最简单常见的写法就是通过枚举分割点,求出每个区间合并的最小花费,从而得到整个区间的最小花费,时间复杂度为O(n^3),核心代码如下:

        for (int i = ; i < n; i++)
{
for (int j = ; j + i <= n; j++)
{
int e = j + i;
dp[j][e] = inf;
for (int k = j; k + <= e; k++)
{
dp[j][e] = min(dp[j][e], dp[j][k] + dp[k + ][e] + sum[e] - sum[j - ]);
}
}
}

四边形优化

对于函数f[][](在这里可以看作数组),如果满足 f[a][c] + f[b][d] <= f[b][c] + f[a][d],则f满足四边形不等式,即 f[i][j-1] <= f[i][j] <= f[i+1][j]

记s[i][j]表示取得dp[i][j]的最优解的分割点,稍加证明即可发现其满足四边形不等式

注意到,在朴素写法中,我们需要枚举分割点来求解:dp[i][j] = max(dp[i][j],dp[i][k]+dp[k+1][j] + sum[j] - sum[i-1]) {i <= k <= j ) ,而因为 s[i][j-1] <= s[i][j] <= s[i+1][j],所以我们枚举的最优分割点s[i][j]的范围即[ s[i][j-1],s[i+1][j] ] ,所以我们枚举分割点的时候,就直接枚举区间[ s[i][j-1],s[i+1][j] ] 而不用枚举[i,j],这样一来,时间复杂度就是O(n^2),核心代码如下:

    for (int i = ; i <= n; i++)                //枚举右端点-左端点的值
{
for (int j = ; j + i <= n; j++) //枚举区间左端点
{
int e = j + i; //区间右端点
dp[j][e] = inf;
for (int k = s[j][e - ]; k <= s[j + ][e]; k++)
{
if (dp[j][e] > dp[j][k] + dp[k + ][e] + sum[e] - sum[j - ])
{
dp[j][e] = dp[j][k] + dp[k + ][e] + sum[e] - sum[j - ];
s[j][e] = k;
}
}
}
}

GarsiaWachs算法

这个方法是我觉得最优的解法了,时间复杂度为O(nlogn),比四边形优化更快,具体原理理解不足,就先贴上大佬的解析吧

解决这类问题的大概步骤是:

0.证明w满足四边形不等式,这里w是m的附属量,形如m[i,j]=opt{m[i,k]+m[k,j]+w[i,j]},此时大多要先证明w满足条件才能进一步证明m满足条件

1.证明m满足四边形不等式

2.证明s[i,j-1]≤s[i,j]≤s[i+1,j]

设序列是stone[],从左往右,找一个满足stone[k-1] <= stone[k+1]的k,找到后合并stone[k]和stone[k-1],再从当前位置开始向左找最大的j,使其满足stone[j] > stone[k]+stone[k-1],插到j的后面就行。一直重复,直到只剩下一堆石子就可以了。在这个过程中,可以假设stone[-1]和stone[n]是正无穷的。

举个例子:186 64 35 32 103

因为35<103,所以最小的k是3,我们先把35和32删除,得到他们的和67,并向前寻找一个第一个超过67的数,把67插入到他后面,得到:186 67 64 103,现在由5个数变为4个数了,继续:186 131 103,现在k=2(别忘了,设A[-1]和A[n]等于正无穷大)234 186,最后得到420。最后的答案呢?就是各次合并的重量之和,即420+234+131+67=852。

基本思想是通过树的最优性得到一个节点间深度的约束,之后证明操作一次之后的解可以和原来的解一一对应,并保证节点移动之后他所在的深度不会改变。具体实现这个算法需要一点技巧,精髓在于不停快速寻找最小的k,即维护一个“2-递减序列”朴素的实现的时间复杂度是O(n*n),但可以用一个平衡树来优化,使得最终复杂度为O(nlogn)。
(解析出自:https://blog.csdn.net/bao___zi/article/details/81913096)

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
#include<string>
#include<fstream>
#include<vector>
#include<stack>
#include <map>
#include <iomanip> #define bug cout << "**********" << endl
#define show(x, y) cout<<"["<<x<<","<<y<<"] "
#define LOCAL = 1;
using namespace std;
typedef long long ll;
const int inf = 1e9 + ;
const ll mod = 1e9 + ;
const int Max = 4e4 + ; int n;
int val[], len;
int sum; void combine(int k)
{
int add = val[k] + val[k - ]; //合并
sum += add; //累加合并的值 for (int i = k; i < len; i++) //k之后的数前移一个位置
{
val[i] = val[i + ];
}
len--; //合并后,总长度减一
int pos = k - ;
while (pos > && val[pos - ] < add) //找到k前的第一个大于add的数
{
val[pos] = val[pos - ];
pos--;
}
val[pos] = add; //插入第一个比add大的数后面
while (pos >= && val[pos] >= val[pos - ])
{
int res = len - pos; //记录pos之后的元素个数,相当于跳过了,后面要补上
combine(pos - ); //合并
pos = len - res; //后面那一段仍然跳过
}
} int main()
{
scanf("%d", &n);
for (int i = ; i < n; i++)
{
scanf("%d", val + i);
}
len = ; //跳过第一个
sum = ;
for (int i = ; i < n; i++)
{
val[len++] = val[i]; //将值加在尾部
while (len >= && val[len - ] < val[len - ])
{
combine(len - );
}
}
while (len > )
combine(len - );
printf("%d\n", sum);
return ;
}

石子合并-环形版

(点击此处查看例题)

相比于直线版的,环形版可以从每一个点j开始,合并其后i堆石子为一堆,1 <= i <= n-1 ,合并的区间长度不受j的影响,而直线版的i满足: 1 <= i <= n - j ,合并的区间收到j的影响,那么我们就将整个环拆成n段链,求出合并这n段链中得分最大(小)者即可,其余操作和直线版一样

朴素写法

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
#include<string>
#include<fstream>
#include<vector>
#include<stack>
#include <map>
#include <iomanip> #define bug cout << "**********" << endl
#define show(x, y) cout<<"["<<x<<","<<y<<"] "
#define LOCAL = 1;
using namespace std;
typedef long long ll;
const int inf = 1e7 + ;
const ll mod = 1e9 + ;
const int Max = 2e2 + ; int n;
int a[];
int sum[];
int dp1[][], dp2[][]; int main()
{
#ifdef LOCAL
// freopen("input.txt", "r", stdin);
// freopen("output.txt", "w", stdout);
#endif
scanf("%d", &n);
for (int i = ; i <= * n; i++)
{
if (i <= n)
scanf("%d", a + i), a[i + n] = a[i]; sum[i] = sum[i - ] + a[i];
}
for (int i = ; i < n; i++)
{
for (int j = ; j + i <= * n; j++)
{
int e = i + j;
dp1[j][e] = inf;
dp2[j][e] = -;
for (int k = j; k + <= e; k++)
{
dp1[j][e] = min(dp1[j][e], dp1[j][k] + dp1[k + ][e] + sum[e] - sum[j - ]);
dp2[j][e] = max(dp2[j][e], dp2[j][k] + dp2[k + ][e] + sum[e] - sum[j - ]);
}
}
}
int min_val = inf, max_val = ;
for (int i = ; i <= n; i++)
{
min_val = min(min_val, dp1[i][i + n - ]);
max_val = max(max_val, dp2[i][i + n - ]);
}
printf("%d\n%d\n", min_val, max_val);
return ;
}

四边形优化写法(目前只能求最小代价)

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
#include<string>
#include<fstream>
#include<vector>
#include<stack>
#include <map>
#include <iomanip> #define bug cout << "**********" << endl
#define show(x, y) cout<<"["<<x<<","<<y<<"] "
#define LOCAL = 1;
using namespace std;
typedef long long ll;
const int inf = 1e7 + ;
const ll mod = 1e9 + ;
const int Max = 1e3 + ; int n;
int a[Max<<];
int sum[Max];
int dp[Max][Max],s[Max][Max]; int main()
{
#ifdef LOCAL
// freopen("input.txt", "r", stdin);
// freopen("output.txt", "w", stdout);
#endif
scanf("%d", &n);
for (int i = ; i <= * n; i++)
{
if (i <= n)
scanf("%d", a + i), a[i + n] = a[i]; sum[i] = sum[i - ] + a[i];
s[i][i] = i;
}
for (int i = ; i < n; i++)
{
for (int j = ; j + i <= * n; j++)
{
int e = i + j;
dp[j][e] = inf;
for (int k = s[j][e-]; k <= s[j+][e]; k++)
{
if(dp[j][e] > dp[j][k] + dp[k + ][e] + sum[e] - sum[j - ])
{
dp[j][e] = dp[j][k] + dp[k + ][e] + sum[e] - sum[j - ];
s[j][e] = k;
}
}
}
}
int min_val = inf;
for (int i = ; i <= n; i++)
{
min_val = min(min_val, dp[i][i + n - ]);
}
printf("%d\n", min_val);
return ;
}

石子合并(直线版+环形版)&(朴素写法+四边形优化+GarsiaWachs算法)的更多相关文章

  1. 石子合并2(环形求最优解 区间dp)

    题目描述 在一个圆形操场的四周摆放N堆石子,现要将石子有次序地合并成一堆.规定每次只能选相邻的2堆合并成新的一堆,并将新的一堆的石子数,记为该次合并的得分. 试设计出1个算法,计算出将N堆石子合并成1 ...

  2. 石子合并(四边形不等式优化dp) POJ1160

    该来的总是要来的———————— 经典问题,石子合并. 对于 f[i][j]= min{f[i][k]+f[k+1][j]+w[i][j]} From 黑书 凸四边形不等式:w[a][c]+w[b][ ...

  3. HRBUST - 1819 石子合并问题--圆形版(区间dp+环形+四边形优化)

    石子合并问题--圆形版 在圆形操场上摆放着一行共n堆的石子.现要将石子有序地合并成一堆.规定每次只能选相邻的两堆合并成新的一堆,并将新的一堆石子数记为该次合并的得分.请编辑计算出将n堆石子合并成一堆的 ...

  4. HRBUST 1818 石子合并问题--直线版

    石子合并问题--直线版 Time Limit: 1000ms Memory Limit: 32768KB This problem will be judged on HRBUST. Original ...

  5. HRBUST 1819 石子合并问题--圆形版

    石子合并问题--圆形版 Time Limit: 1000ms Memory Limit: 32768KB This problem will be judged on HRBUST. Original ...

  6. HDU 3506 (环形石子合并)区间dp+四边形优化

    Monkey Party Time Limit: 4000/2000 MS (Java/Others)    Memory Limit: 131072/65536 K (Java/Others)Tot ...

  7. RQNOJ 490 环形石子合并

    题目链接:https://www.rqnoj.cn/problem/490 题目描述 在一个园形操场的四周摆放N堆石子,现要将石子有次序地合并成一堆.规定每次只能选相邻的2堆合并成新的一堆,并将新的一 ...

  8. 洛谷P1880 石子合并(环形石子合并 区间DP)

    题目描述 在一个圆形操场的四周摆放N堆石子,现要将石子有次序地合并成一堆.规定每次只能选相邻的2堆合并成新的一堆,并将新的一堆的石子数,记为该次合并的得分. 试设计出1个算法,计算出将N堆石子合并成1 ...

  9. HDU3506环形石子合并问题

    HDU3506环形石子合并问题 线性的石子合并问题比较好理解,环形的转成线性的方法就是扩展数组 1 2 3 . . . n 1 2 3 ... n 依据是我们最优的取值可以是 1 --- n也能是 2 ...

随机推荐

  1. 第一次尝试学习java 安装jdk 与配置环境变量 写第一个java程序 并运行

    第一次学习java,今天知道了java之父叫  詹姆斯.高司令 其它的记不住太多,首先我们先来安装jdk 百度搜索jdk12   (现在的jdk为12版本)安装稳定版 找到javaSE12X..  下 ...

  2. ****题(alb)

    sol:较简单的dp题,n4随便写写,n3需要加一个小优化 int i,j,k,i1,j1,i2,j2; memset(dp,,sizeof dp); ;i<n;i+=) dp[][i][i+] ...

  3. html页面之间相互传值

    常见的在页面登录过后会获得一个token值然后页面跳转时传给下一个页面 sessionStorage.setItem("token",result.token);//传输token ...

  4. Codeforces 959D. Mahmoud and Ehab and another array construction task(构造, 简单数论)

    Codeforces 959D. Mahmoud and Ehab and another array construction task 题意 构造一个任意两个数都互质的序列,使其字典序大等于a序列 ...

  5. Python选择指定文件夹的文件然后复制出其中几个文件到新的文件夹

    """ 要求: 1.读取cdm文件的所有子文件夹,然后每个文件夹里面是抽出一个一个mp3后缀的文件. 遍历所有的子文件,然后将这些mp3文件,保存到一个新的文件夹.文件夹 ...

  6. HearthBuddy 突袭 rush

    https://hearthstone.gamepedia.com/Rush Rush is an ability allowing a minion to attack other minions ...

  7. Mac下持续集成-自动发送邮件

    找到下面这项填写邮件地址 注意下面绿色标记的邮箱要和上面的一致,否则会报错 如果两个绿色标记的邮箱不一致会报这样的错: ---------------------------------------- ...

  8. PCL中有哪些可用的PointT类型(4)

    博客转载自:http://www.pclcn.org/study/shownews.php?lang=cn&id=269 PointWithViewpoint - float x, y, z, ...

  9. FullSync不支持中文文件名

    FullSync,能实现多种方式.协议的目录同步软件,但不支持中文文件名.

  10. B站动手学深度学习第十八课:seq2seq(编码器和解码器)和注意力机制

    from mxnet import nd h_forward = nd.array([1,2]) h_backward = nd.array([3,4]) h_bi = nd.concat(h_for ...