1.Max sum plus plus
本题是一个M段子段的最大字段和的问题,也就是把一个数列分成M小节使得每小节总和最大。关于这个问题先从最大子段和(单段)开始说起。
最大子段和(子段是连续子数列)的求解意味着对于数列中一个元素,要么归于前面已经成型的一个子段,要么自成一派创立新的子段。这个抉择取决于这两个的权值的大小,如果成型的子段的和较大,那么就让它加入前面的子段,否则就自己作为新子段的开头。
方程:DP[I]=MAX(DP[I-1]+A[I],A[I])
M子段的最大子段和问题是最大子段和的一个推广,与之类似的有最大子矩阵问题。
如果要求解M段子段,那么对于数列内任意一个元素也是都有两种选择:归于前面的第i段或者独树一帜,找到第i-1段的最大值所在并传承过来,然后自己作为第i段的起点。
即为:DP[I][J]=MAX(DP[I][J-1],DP[I-1][SOMEWHERE])+CURRENT[J],其中DP[I][J]代表当前已经组建了I段,遍历到第J个元素。SOMEWHERE是第i-1段的最大值所在处。
不过对于本题而言,m的大小是一个悬而未决的问题(题目没给),并且n达到了百万级,所以单纯使用上述方程是在内存限制条件下无法求解本题的。所以就要优化空间。
优化空间不是随意的。比如本题使用的滚动数组的优化条件就是当前状态并没有与太多状态相关联,比如说用多少枚硬币表示多少现金,这个DP[I][J]就是无法优化的,因为硬币面值的不同,现金的总额可以由很多状态转移而来,倘若优化就直接破坏了这个数组的信息存储能力,因为很多状态的信息无法被正确的表达,而子状态的信息存储是DP的核心。
幸运的是,通过分析可以发现本题中的状态转移规律,总是可以发现一个事情:当分析某个元素时,这个元素要么归于前面一个子段,要么自己创立一个子段,它跟第i-2及以前的子段没有直接关系,所以可以直接把代表字段数的第一维压缩为一个指示标志:0与1,分别代表两个不同的子段。
得到了一个新的方程:DP[1][J]=MAX(DP[1][J-1],DP[0][K])+CURRENT[I]。
在实现过程中,t(当前段)和1-t(前一段)需要更迭,以达成每一段都可以被访问的目的。
模板:
import java.util.*;
import java.io.*;
import java.math.*;
import java.text.*;
public class Main
{
static int ar[]=new int[15000];
static long dp[][]=new long[3][15000];
public static void main(String args[])throws IOException
{
BufferedReader bf=new BufferedReader(new InputStreamReader(System.in));
BufferedWriter bw=new BufferedWriter(new OutputStreamWriter(System.out),1<<16);
String tmp=bf.readLine();
String tmps[]=tmp.trim().split(" ");
int n=Integer.parseInt(tmps[0]),m=Integer.parseInt(tmps[1]);
for(int i=1;i<=n;i++)
{
tmp=bf.readLine();
ar[i]=Integer.parseInt(tmp);
}
int current=1;
for(int i=1;i<=m;i++)
{
dp[current][i]=dp[1-current][i-1]+ar[i];//意味着这是以第m段开始,第一个元素是ar[i]
long tmp9=dp[1-current][i-1];//初始化最大值
for(int j=i+1;j<=n-m+i(一个子段的长度顶多为n-m);j++)
{
tmp9=Math.max(tmp9,dp[1-current][j-1]);//上一段至j-1之前的最大值
dp[current][j]=Math.max(tmp9,dp[current][j-1])+ar[j];
}
current=1-current;
}
long ans=-1000000002;
for(int i=1;i<=n;i++)
ans=Math.max(ans,Math.max(dp[0][i],dp[1][i]));
bw.write(Long.toString(ans));
bw.newLine();
bw.flush();
bw.close();
}
}
可以看出,尽管DP[0][K]的K下标是一个不定值,但是它本身一定代表着在J-1之前的i-1段的最大值。所以如果在过程中就记录这个最大值,就可以直接略去遍历K次这个环节。如何实现?用一个新数组表示在分成I段时前J-1个元素中所计算出的最大I字段和,然后状态转移方程就变成了DP[1][J]=MAX[DP[1][J-1],PREVIOUS[J-1])+CURRENT[I]
显然前面这个1已经没用了,可以直接压缩掉,最终的方程就是DP[J]=MAX(DP[J-1],PREVIOUS[J-1]),其中previous数组代表分成i-1段时J-1个元素内的最大值,也就是原来的DP[I-1][K].
不过滚动数组意味着多次覆盖,所以如果要准确的求某个状态对应的值(已知两个下标求值),还是慎用此优化。
//#include<pch.h>
#include <iostream>
#include <cstdio>
#include <bits/stdc++.h>
#include <queue>
#include <map>
#include <algorithm>
#include <stack>
#include <iomanip>
#include <cstring>
#include <cmath>
#define DETERMINATION main
#pragma GCC optimize(2)
#pragma warning(disable:4996)
#define lldin(a) scanf("%lld", &a)
#define println(a) printf("%lld\n", a)
#define print(a) printf("%lld ", a)
#define reset(a, b) memset(a, b, sizeof(a))
#define debug cout<<"procedures above are available"<<endl;
#define BigInteger __int128
using namespace std;
const int INF = 0x3f3f3f3f;
const double PI = acos(-1);
typedef long long ll;
typedef unsigned long long ull;
typedef long double ld;
const int mod = 10000000;
const int tool_const = 19991126;
const int tool_const2 = 33;
//template<typename T>
//inline BigInteger nextLong()
//{
// BigInteger tmp = 0, si = 1;
// char c;
// c = getchar();
// while (!isdigit(c))
// {
// if (c == '-')
// si = -1;
// c = getchar();
// }
// while (isdigit(c))
// {
// tmp = tmp * 10 + c - '0';
// c = getchar();
// }
// return si * tmp;
//}
//std::ostream& operator<<(std::ostream& os, __int128 T)
//{
// if (T<0) os<<"-";if (T>=10 ) os<<T/10;if (T<=-10) os<<(-(T/10));
// return os<<( (int) (T%10) >0 ? (int) (T%10) : -(int) (T%10) ) ;
//}
//void output(BigInteger x)
//{
// if (x < 0)
// {
// x = -x;
// putchar('-');
// }
// if (x > 9) output(x / 10);
// putchar(x % 10 + '0');
//}
/**Maintain your determination.Nobody knows the magnificent landscape
at his destination before the arrival with stumble.**/
/**Last Remote**/
ll previous[1200000], dp[1200000];
ll ar[1200000];
int DETERMINATION()
{
//ios::sync_with_stdio(false);
//cin.tie(0), cout.tie(0);
ll m, n;
while (~scanf("%lld %lld", &m, &n))
{
for (int i = 1; i <= n; i++)
lldin(ar[i]);
for (int i = 0; i <= n+3; i++)
previous[i] = 0, dp[i] = 0;
ll tmp = -3;
for (int i = 1; i <= m; i++)
{
tmp = -INF;
for (int j = i; j <= n; j++)
{
dp[j] = max(dp[j - 1], previous[j - 1]) + ar[j];
previous[j - 1] = tmp;//把第i-1轮的数据更新为第i轮的数据
tmp = max(tmp, dp[j]);
}
}
println(tmp);
}
return 0;
}
3 Monkey and Banana
A group of researchers are designing an experiment to test the IQ of a monkey. They will hang a banana at the roof of a building, and at the mean time, provide the monkey with some blocks. If the monkey is clever enough, it shall be able to reach the banana by placing one block on the top another to build a tower and climb up to get its favorite food.
一队研究员正在设计一个测量猴子智商的实验方案。他们把一个香蕉挂在一个建筑物的屋顶上,与此同时研究员给猴子扔了一堆石头。如果猴子足够聪明的话,它应该能够到香蕉,通过把一个石头垒在另一个石头上并爬到顶上的方式。
The researchers have n types of blocks, and an unlimited supply of blocks of each type. Each type-i block was a rectangular solid with linear dimensions (xi, yi, zi). A block could be reoriented so that any two of its three dimensions determined the dimensions of the base and the other dimension was the height.
研究员有n种石头,以及这几种石头的无限制供应,每种石头都是一个矩形固体块,它们的线性三维数据是xi,yi,zi。一个石头可以换个方向放置,所以它的任意两维都可以指代底面积,另一维指代高。
They want to make sure that the tallest tower possible by stacking blocks can reach the roof. The problem is that, in building a tower, one block could only be placed on top of another block as long as the two base dimensions of the upper block were both strictly smaller than the corresponding base dimensions of the lower block because there has to be some space for the monkey to step on. This meant, for example, that blocks oriented to have equal-sized bases couldn't be stacked.
他们想确认垒石头能到达的最大高度。问题是,垒石头的时候,一个石头只能垒在一个长宽都比他大的石头上。因为这需要留点空间让猴子能踩着上去。比如,这意味着相同底面积的石块不能放在一起。
本题像是那种矩形嵌套的DAG模型,但实际上也可以当做最长上升子序列问题解决。由于石头可以任意放置,所以根据放置的不同,一个石头就有了六种属性,因为石头是无限制供应的,所以这六种可能性可以直接当作六种不同的石头即可。
然后根据长宽降序排序,利用LIS相关知识解决即可。
#include<pch.h>
#include <iostream>
#include <cstdio>
#include <bits/stdc++.h>
#include <queue>
#include <map>
#include <algorithm>
#include <stack>
#include <iomanip>
#include <cstring>
#include <cmath>
#define DETERMINATION main
#pragma GCC optimize(2)
#pragma warning(disable:4996)
#define lldin(a) scanf("%lld", &a)
#define println(a) printf("%lld\n", a)
#define print(a) printf("%lld ", a)
#define reset(a, b) memset(a, b, sizeof(a))
#define debug cout<<"procedures above are available"<<endl;
#define BigInteger __int128
using namespace std;
const int INF = 0x3f3f3f3f;
const double PI = acos(-1);
typedef long long ll;
typedef unsigned long long ull;
typedef long double ld;
const int mod = 10000000;
const int tool_const = 19991126;
const int tool_const2 = 33;
//template<typename T>
//inline BigInteger nextLong()
//{
// BigInteger tmp = 0, si = 1;
// char c;
// c = getchar();
// while (!isdigit(c))
// {
// if (c == '-')
// si = -1;
// c = getchar();
// }
// while (isdigit(c))
// {
// tmp = tmp * 10 + c - '0';
// c = getchar();
// }
// return si * tmp;
//}
//std::ostream& operator<<(std::ostream& os, __int128 T)
//{
// if (T<0) os<<"-";if (T>=10 )

博客介绍了如何解决最大子段和问题及其推广到多段子序列的问题,探讨了如何优化空间以适应大规模数据。还讲解了其他动态规划问题,如猴子和香蕉问题、做作业问题、超级跳跳跳棋游戏等,提供了解题策略和模板,包括最长上升子序列问题的解法和状态压缩DP的应用。
最低0.47元/天 解锁文章
397

被折叠的 条评论
为什么被折叠?



