将 n 堆石子绕圆形操场排放,现要将石子有序地合并成一堆。
规定每次只能选相邻的两堆合并成新的一堆,并将新的一堆的石子数记做该次合并的得分。
请编写一个程序,读入堆数 n 及每堆的石子数,并进行如下计算:
选择一种合并石子的方案,使得做 n−1 次合并得分总和最大。
选择一种合并石子的方案,使得做 n−1 次合并得分总和最小。
输入格式
第一行包含整数 n,表示共有 n 堆石子。
第二行包含 n 个整数,分别表示每堆石子的数量。
输出格式
输出共两行:
第一行为合并得分总和最小值,
第二行为合并得分总和最大值。
数据范围
1≤n≤200
输入样例:
4
4 5 9 4
输出样例:
43
54
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 440,INF=0x3f3f3f3f;
int f[N][N],g[N][N],n,w[N+N],maxv=-INF,minv=INF;
int sum[2*N];
int main(){
cin>>n;
for(int i=1;i<=n;i++){
cin>>w[i];
w[i+n]=w[i];
}
for(int i=1;i<=n*2;i++)sum[i]=sum[i-1]+w[i];
memset(f,-0x3f,sizeof f);
memset(g,0x3f,sizeof g);
for(int len=1;len<=n;len++){
for(int l=1;l+len-1<=2*n;l++){
int r=l+len-1;
if(len==1)f[l][r]=g[l][r]=0;
else{
for(int k=l;k<r;k++){
f[l][r]=max(f[l][r],f[l][k]+f[k+1][r]+sum[r]-sum[l-1]);
g[l][r]=min(g[l][r],g[l][k]+g[k+1][r]+sum[r]-sum[l-1]);
}
}
}
}
for(int i=1;i<=n;i++){
maxv=max(maxv,f[i][i+n-1]);
minv=min(minv,g[i][i+n-1]);
}
cout<<minv<<endl<<maxv;
}
在 Mars 星球上,每个 Mars 人都随身佩带着一串能量项链,在项链上有 N 颗能量珠。
能量珠是一颗有头标记与尾标记的珠子,这些标记对应着某个正整数。
并且,对于相邻的两颗珠子,前一颗珠子的尾标记一定等于后一颗珠子的头标记。
因为只有这样,通过吸盘(吸盘是 Mars 人吸收能量的一种器官)的作用,这两颗珠子才能聚合成一颗珠子,同时释放出可以被吸盘吸收的能量。
如果前一颗能量珠的头标记为 m,尾标记为 r,后一颗能量珠的头标记为 r,尾标记为 n,则聚合后释放的能量为 m×r×n(Mars 单位),新产生的珠子的头标记为 m,尾标记为 n。
需要时,Mars 人就用吸盘夹住相邻的两颗珠子,通过聚合得到能量,直到项链上只剩下一颗珠子为止。
显然,不同的聚合顺序得到的总能量是不同的,请你设计一个聚合顺序,使一串项链释放出的总能量最大。
例如:设 N=4,4 颗珠子的头标记与尾标记依次为 (2,3)(3,5)(5,10)(10,2)。
我们用记号 ⊕ 表示两颗珠子的聚合操作,(j⊕k) 表示第 j,k 两颗珠子聚合后所释放的能量。则
第 4、1 两颗珠子聚合后释放的能量为:(4⊕1)=10×2×3=60。
这一串项链可以得到最优值的一个聚合顺序所释放的总能量为 ((4⊕1)⊕2)⊕3)=10×2×3+10×3×5+10×5×10=710。
输入格式
输入的第一行是一个正整数 N,表示项链上珠子的个数。
第二行是 N 个用空格隔开的正整数,所有的数均不超过 1000,第 i 个数为第 i 颗珠子的头标记,当 i<N 时,第 i 颗珠子的尾标记应该等于第 i+1 颗珠子的头标记,第 N 颗珠子的尾标记应该等于第 1 颗珠子的头标记。
至于珠子的顺序,你可以这样确定:将项链放到桌面上,不要出现交叉,随意指定第一颗珠子,然后按顺时针方向确定其他珠子的顺序。
输出格式
输出只有一行,是一个正整数 E,为一个最优聚合顺序所释放的总能量。
数据范围
4≤N≤100,
1≤E≤2.1×109
输入样例:
4
2 3 5 10
输出样例:
710
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 202;
int w[N],dp[N][N];
int n;
int main(){
cin>>n;
for(int i=1;i<=n;i++){
cin>>w[i];
w[i+n]=w[i];
}
//memset(dp,-0x3f,sizeof dp);
//len表示节点数量
for(int len=3;len<=n+1;len++){
for(int L=1;L+len-1<=2*n;L++){
int R=L+len-1;
for(int k=L+1;k<=R-1;k++){
dp[L][R]=max(dp[L][R],dp[L][k]+dp[k][R]+w[L]*w[k]*w[R]);
}
}
}
int ans=0;
for(int i=1;i<=n;i++)ans=max(ans,dp[i][i+n]);
cout<<ans<<endl;
}
给定一个具有 N 个顶点的凸多边形,将顶点从 1 至 N 标号,每个顶点的权值都是一个正整数。
将这个凸多边形划分成 N−2 个互不相交的三角形,对于每个三角形,其三个顶点的权值相乘都可得到一个权值乘积,试求所有三角形的顶点权值乘积之和至少为多少。
输入格式
第一行包含整数 N,表示顶点数量。
第二行包含 N 个整数,依次为顶点 1 至顶点 N 的权值。
输出格式
输出仅一行,为所有三角形的顶点权值乘积之和的最小值。
数据范围
N≤50,
数据保证所有顶点的权值都小于109
输入样例:
5
121 122 123 245 231
输出样例:
12214884
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long LL;
const int N = 55, M = 35, INF = 1e9;
int n;
int w[N];
LL f[N][N][M];
void add(LL a[], LL b[])
{
static LL c[M];
memset(c, 0, sizeof c);
for (int i = 0, t = 0; i < M; i ++ )
{
t += a[i] + b[i];
c[i] = t % 10;
t /= 10;
}
memcpy(a, c, sizeof c);
}
void mul(LL a[], LL b)
{
static LL c[M];
memset(c, 0, sizeof c);
LL t = 0;
for (int i = 0; i < M; i ++ )
{
t += a[i] * b;
c[i] = t % 10;
t /= 10;
}
memcpy(a, c, sizeof c);
}
int cmp(LL a[], LL b[])
{
for (int i = M - 1; i >= 0; i -- )
if (a[i] > b[i]) return 1;
else if (a[i] < b[i]) return -1;
return 0;
}
void print(LL a[])
{
int k = M - 1;
while (k && !a[k]) k -- ;
while (k >= 0) cout << a[k -- ];
cout << endl;
}
int main()
{
cin >> n;
for (int i = 1; i <= n; i ++ ) cin >> w[i];
LL temp[M];
for (int len = 3; len <= n; len ++ )
for (int l = 1; l + len - 1 <= n; l ++ )
{
int r = l + len - 1;
f[l][r][M - 1] = 1;
for (int k = l + 1; k < r; k ++ )
{
memset(temp, 0, sizeof temp);
temp[0] = w[l];
mul(temp, w[k]);
mul(temp, w[r]);
add(temp, f[l][k]);
add(temp, f[k][r]);
if (cmp(f[l][r], temp) > 0)
memcpy(f[l][r], temp, sizeof temp);
}
}
print(f[1][n]);
return 0;
}
作者:yxc
链接:https://www.acwing.com/activity/content/code/content/124469/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
设一个 n 个节点的二叉树 tree 的中序遍历为(1,2,3,…,n),其中数字 1,2,3,…,n 为节点编号。
每个节点都有一个分数(均为正整数),记第 i 个节点的分数为 di,tree 及它的每个子树都有一个加分,任一棵子树 subtree(也包含 tree 本身)的加分计算方法如下:
subtree的左子树的加分 × subtree的右子树的加分 + subtree的根的分数
若某个子树为空,规定其加分为 1。
叶子的加分就是叶节点本身的分数,不考虑它的空子树。
试求一棵符合中序遍历为(1,2,3,…,n)且加分最高的二叉树 tree。
要求输出:
(1)tree的最高加分
(2)tree的前序遍历
输入格式
第 1 行:一个整数 n,为节点个数。
第 2 行:n 个用空格隔开的整数,为每个节点的分数(0<分数<100)。
输出格式
第 1 行:一个整数,为最高加分(结果不会超过int范围)。
第 2 行:n
个用空格隔开的整数,为该树的前序遍历。如果存在多种方案,则输出字典序最小的方案。
数据范围
n<30
输入样例:
5
5 7 1 2 10
输出样例:
145
3 1 2 4 5
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
int w[100];
int tree[100][100];
int root[100][100];
int n;
int ans;
int dfs(int l,int r){
if(tree[l][r])return tree[l][r];
if(l==r)return w[l];
if(l>r)return 1;
for(int i=l;i<=r;i++){
int t=dfs(l,i-1)*dfs(i+1,r)+w[i];
if(t>tree[l][r]){
tree[l][r]=t;
root[l][r]=i;
}
}
return tree[l][r];
}
void dg(int l,int r){
if(l>r)return;
if(l==r){printf("%d ",root[l][r]);return;}
printf("%d ",root[l][r]);
dg(l,root[l][r]-1);
dg(root[l][r]+1,r);
}
int main(){
cin>>n;
for(int i=1;i<=n;i++)cin>>w[i];
for(int i=1;i<=n;i++)root[i][i]=i;
cout<<dfs(1,n)<<endl;
dg(1,n);
}
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
typedef pair<int, int> PII;
const int N = 50;
int n;
int w[N];
unsigned f[N][N];
int root[N][N];
void dfs(int l, int r)
{
if (l > r) return;
int k = root[l][r];
printf("%d ", k);
dfs(l, k - 1);
dfs(k + 1, r);
}
int main()
{
scanf("%d", &n);
for (int i = 1; i <= n; i ++ ) scanf("%d", &w[i]);
for (int len = 1; len <= n; len ++ )
for (int l = 1; l + len - 1 <= n; l ++ )
{
int r = l + len - 1;
for (int k = l; k <= r; k ++ )
{
int left = k == l ? 1 : f[l][k - 1];
int right = k == r ? 1 : f[k + 1][r];
int score = left * right + w[k];
if (l == r) score = w[k];
if (f[l][r] < score)
{
f[l][r] = score;
root[l][r] = k;
}
}
}
printf("%d\n", f[1][n]);
dfs(1, n);
puts("");
return 0;
}
作者:yxc
链接:https://www.acwing.com/solution/content/3804/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
将一个 8×8 的棋盘进行如下分割:将原棋盘割下一块矩形棋盘并使剩下部分也是矩形,再将剩下的部分继续如此分割,这样割了 (n−1) 次后,连同最后剩下的矩形棋盘共有 n 块矩形棋盘。(每次切割都只能沿着棋盘格子的边进行)
1191_1.jpg
原棋盘上每一格有一个分值,一块矩形棋盘的总分为其所含各格分值之和。
现在需要把棋盘按上述规则分割成 n 块矩形棋盘,并使各矩形棋盘总分的均方差最小。
均方差formula.png ,其中平均值lala.png ,xi 为第 i 块矩形棋盘的总分。
请编程对给出的棋盘及 n,求出均方差的最小值。
输入格式
第 1 行为一个整数 n。
第 2 行至第 9 行每行为 8 个小于 100 的非负整数,表示棋盘上相应格子的分值。每行相邻两数之间用一个空格分隔。
输出格式
输出最小均方差值(四舍五入精确到小数点后三位)。
数据范围
1<n<15
输入样例:
3
1 1 1 1 1 1 1 3
1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 0
1 1 1 1 1 1 0 3
输出样例:
1.633
本题的大意是给定一个 8∗88∗8 的棋盘,对这个棋盘进行 n−1(1<n<15)n−1(1<n<15) 次划分,要求每次划分后,只可以选择两块子矩阵的其中一块继续划分。
完成划分后,一共会得到 nn 块棋盘。
原棋盘上每一格有一个分值,一块矩形棋盘的总分为其所含各格分值之和。
现在需要把棋盘按上述规则分割成 nn 块矩形棋盘,并使各矩形棋盘总分的均方差最小。
显然可以观察到一个二维 区间DP区间DP 的模型 (不对,二维是不是应该叫面积DP(滑稽))
根据题意,我们可以得到的信息是
1)一共可以对棋盘进行 n−1n−1 次划分得到 nn 个子棋盘
2)对所有的子棋盘可以求得的平均数 x¯¯¯=∑ni=1xinx¯=∑i=1nxin
3)要求总分的均方差最小 σ=∑ni=1(xi−x¯¯¯)2n−−−−−−−−−−−−√σ=∑i=1n(xi−x¯)2n
为了使方差 σσ 最小,因此我们首先需要对 σσ 的表达式进行化简
推导过程:
σ2=1n(∑ni=1x2i−2∑ni=1xi⋅x¯¯¯+∑ni=1x¯¯¯2)σ2=1n(∑i=1nxi2−2∑i=1nxi⋅x¯+∑i=1nx¯2)
σ2=1n∑ni=1x2i−2x¯¯¯1n∑ni=1xi+1n⋅nx¯¯¯2σ2=1n∑i=1nxi2−2x¯1n∑i=1nxi+1n⋅nx¯2
σ2=1n∑ni=1x2i−2x¯¯¯2+x¯¯¯2σ2=1n∑i=1nxi2−2x¯2+x¯2
σ2=1n∑ni=1x2i−x¯¯¯2σ2=1n∑i=1nxi2−x¯2
由于 x¯¯¯x¯ 是常数,因此显然可以观察到 σσ 与 xixi 是同增的
为了最终方差是最小值,我们需要满足所有子矩阵的值的平方最小
求子矩阵的值的平方,我们会在代码中的get()方法实现
f[x1][y1][x2][y2][k]状态表示:
1) 集合:划分到k−1k−1个的子矩阵,是以(x1,y1)(x1,y1)为左上角,(x2,y2)(x2,y2)为右下角
2) 属性:σ=∑ni=1(xi−x¯¯¯)2n−−−−−−−−−−−−√σ=∑i=1n(xi−x¯)2n最小值 Min
状态计算:
1)集合划分:
/*
1.1) 横着切:
1.1.1)以(x1, y1)为左上角,以(x1, y2)为右下角 或 以(x1+1, y1)为左上角,以(x2, y2)为右下角
1.1.2)以(x1, y1)为左上角,以(x1+1, y2)为右下角 或 以(x1+2, y1)为左上角,以(x2, y2)为右下角
…
1.1.i)以(x1, y1)为左上角,以(x1+i, y2)为右下角 或 以(x1+i+1, y1)为左上角,以(x2, y2)为右下角
…
1.1.x2-1)以(x1, y1)为左上角,以(x2-1, y2)为右下角 或 以(x2, y1)为左上角,以(x2, y2)为右下角
1.2) 竖着切:
1.2.1)以(x1,y1)为左上角,以(x2, y1)为右下角 或 以(x1, y1)为左上角,以(x2, y2)为右下角
1.2.2)以(x1,y1)为左上角,以(x2, y1+1)为右下角 或 以(x1, y1+2)为左上角,以(x2, y2)为右下角
…
1.2.i)以(x1,y1)为左上角,以(x2, y1+i)为右下角 或 以(x1, y1+i+1)为左上角,以(x2, y2)为右下角
…
1.2.y2-1)以(x1, y1)为左上角,以(x2, y2-1)为右下角 或 以(x1,y2-1)为左上角,以(x2, y2)为右下角
*/
2)状态转移方程
模拟上述集合的划分枚举所有的区间即可
由于dp的方程维数过大,写 55 重迭代太麻烦了,这题采用记忆化搜索
Code
#include <iostream>
#include <cstring>
#include <cmath>
using namespace std;
const int N = 9, M = 15;
const double INF = 1e9;
int n, m = 8;
int s[N][N];
double f[N][N][N][N][M];
double X; //均值x拔
double get(int x1, int y1, int x2, int y2) {
//根号下,求和符号内的部分
double sum = s[x2][y2] - s[x2][y1 - 1] - s[x1 - 1][y2] + s[x1 - 1][y1 - 1] - X;
//还要平方
return (double)sum * sum;
}
double dp(int x1, int y1, int x2, int y2, int k) {
auto &t = f[x1][y1][x2][y2][k];
if (t >= 0) return t;
if (k == 1) return get(x1, y1, x2, y2);
t = INF; //求最小值要初始化成最大值
//横着切
for (int i = x1; i < x2; ++i) {
t = min(t, dp(x1, y1, i, y2, k - 1) + get(i + 1, y1, x2, y2));
t = min(t, dp(i + 1, y1, x2, y2, k - 1) + get(x1, y1, i, y2));
}
//竖着切
for (int i = y1; i < y2; ++i) {
t = min(t, dp(x1, y1, x2, i, k - 1) + get(x1, i + 1, x2, y2));
t = min(t, dp(x1, i + 1, x2, y2, k - 1) + get(x1, y1, x2, i));
}
return t;
}
int main() {
cin >> n;
for (int i = 1; i <= m; ++i) {
for (int j = 1; j <= m; ++j) {
cin >> s[i][j];
s[i][j] += s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1];
}
}
X = (double) s[m][m] / n;
//记忆化搜索,初始化成NaN
memset(f, -1, sizeof f);
//这个就是\sigma的完整计算公式
printf("%.3lf\n", sqrt(dp(1, 1, m, m, n) / n));
return 0;
}
作者:彩色铅笔
链接:https://www.acwing.com/solution/content/34369/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。