解题总结
总结:
近期做的题,思考量很大,但不管怎样最有效的途径都是一点一点思考去解决。C题老鼠花了很长时间一开始做只想套方程模板,调试的过程中越想AC,可能耗费的时间会越长因为根本找不到自己的问题,所以仔细分析慢就等于快。接下来,再来仔细回顾一下做题过程,老鼠体重越大,速度越小。输入多组老鼠weight and v,输出体重严格上升,速度严格下降的最长序列长度和位置。又是一个序列性问题,但有点难道的是两个序列,两个序列想到最长公共子序列,最大子矩阵和,最大m字段和等但在他们之间没有找到可以联系的地方,这是用两个序列去思考。但贪心做过类似的题,题目同样有两个顺序要求,先固定一个简化问题,在这个题中同样适用,于是就转化成求最长上升子序列。还需要依据求最长上升子序列的动态方程来找到。C题我是根据将体重从小到大排序,找到速度的最长上升子序列,之后让输出了状态方程以及数据,来确定找下标的办法,寻找最长子序列下标时要以最长子序列长度和当前最大速度,因为是倒着遍历所以找之前较大的数,通过定义一个较小数不断被大数替换来实现。找到最长子序列的位置记录它们原来的下标,最后输出。这是刚做的一道题,具体实践还存在问题,这道题学到的新知识在于学会找到序列★,看了别人题解知道需要根据自己排序之后的数据加上条件限制输出。
最长上升子序列:
for( i = 2; i <= N; i ++ )
{
int nTmp = 0;
//记录第i个数左边子序列最大长度
for(j = 1; j < i; j ++ )
{
if( b[i] > b[j])
{
if(nTmp < aMaxLen[j])
nTmp = aMaxLen[j];
//找到以第i个数左边数为终点的最长上升子序列长度
}
}
aMaxLen[i] = nTmp + 1; //加上它本身,之后序列的长度
}
int nMax = -1;
for( i = 1; i <= N; i ++ )
if(nMax<aMaxLen[i])
nMax=aMaxLen[i];
cout <<nMax;
另一方式:
状态含义:F [ i ] 代表以 A [ i ] 结尾的 LIS 的长
度
状态转移:F [ i ] = max { F [ j ] + 1 ,F [ i ] } (1 <= j < i,A[ j ] < A[ i ])
初始值:F [ i ] = 1 (1 <= i <= n)
for(int i=1; i<=n; i++)
for(int j=1; j<i; j++)
if(a[j] < a[i])
f[i] = max(f[i], f[j]+1);
for(int i=1; i<=n; i++)
ans = max(ans, f[i]);
printf("%d\n", ans);
总结:
就是d[i]就是以a[i]结尾,在a[i]之前最长上升子序列+1,没有更小的话就是1,所以dp数组中最大长度就是最长子序列。需要通过循环找到最长的长度,与最长公共子序列不同,最长公共子序列动态数组最后的数就是最长的长度。接下来就来回顾一下最长公共子序列,dp作业Q题,Q题现在看来是最简单的最起初的了,只是让我们输出最大长度。而通过了解还会有输出最长公共子序列的要求,它又分为输出任意一个序列和全部输出。接下来是Q题的扩展。
最长公共子序列:
DP[i][j]代表 A串从起点到i位置的字串 和 B串从起点到j位置的字串 的LCS
当A[i] == B[j] 时 :DP[i][j] = DP[i-1][j-1] + 1
当A[i] != B[j] 时 : DP[i][j] = MAX(DP[i-1][j] , DP[i][j-1])
code:
int LCS()
{
dp[0][0]=0
for(int i = 1 ; i < =n ; i++)
for(int j = 1 ; j <= m ; j+++)
if(A[i] == B[j])
dp[i][j] = dp[i-1][j-1]+1;
else
dp[i][j] = max(dp[i-1][j] , dp[i][j-1]);
return dp[n][m];
}
总结一下:
确定一个决定性的序列,该代码决定性的i代表的序列,通过它来找到公共的序列,代码的理解要回到问题的本质,开始遍历,如果两个序列元素相同,那么让其各回到上一个位置,该位置序列长度加一,如果不同,就找出a序列与b序列的上一个和a序列上一个元素与b序列该元素的最长公共子序列的长度。那么最后的位置就是最长公共子序列的个数了,最最基础的。
Q题扩展:
要求:任意输出一个公共子序列。(需要用到递归的思想,还有就是标记)
样例输入
ABCBDAB
BDCABA
样例输出
4
BCBA
找来图片帮助理解。
个人理解:
int f[205][205],g[205][205];//g存储是那种状态转移来的,分别用-1,0,1来表示
void getans(int n,int m){
if(!n||!m) return;//m或n为0结束
if(g[n][m]==1){//选择这种情况输出进行输出
getans(n-1,m-1);//递归到上一个状态
printf("%c",a[n]);//会倒向输出
}
else if(g[n][m]==-1) getans(n-1,m);
else getans(n,m-1);
}
//主函数
int lena=strlen(a+1),lenb=strlen(b+1);
for(int i=1;i<=lena;++i){
for(int j=1;j<=lenb;++j){//把max分开成两个if
if(f[i-1][j]>f[i][j]) f[i][j]=f[i-1][j],g[i][j]=-1;//标记
if(f[i][j-1]>f[i][j]) f[i][j]=f[i][j-1],g[i][j]=0;//标记
if(a[i]==b[j]){
if(f[i-1][j-1]+1>f[i][j]){
g[i][j]=1;//标记
f[i][j]=f[i-1][j-1]+1;
}
}
}
}
if(!f[lena][lenb]) printf("0");//无公共子序列
else{
printf("%d\n",f[lena][lenb]);
getans(lena,lenb);//调用递归函数
}
P题与以上的输出序列有些相似,只不过是最大字段和收尾子段位置的输出。与输出最长公共子序列的思想是类似的,都要将情况细分开来进行标记,位置的更替。但没有必要用到递归。因为子段本来就是连续的,而且还是一维。在第一次做的时候很难想清楚,因为想到当你从头开始遍历找最大和时,即使找到了最大和也不能确定之后有没有更大的。但其实是可以确定的,因为sum被当前的最大字段和替代,所以很显然之后就没有更大的啦,所有不用担心坐标再一次被更新。
int sum=0,num=1;
for(int i=1; i<=n; i++)
{
sum+=a[i];
if(maxx<sum)
{
maxx=sum;
k=num;//k会是一直不变的,直到开始一个新的子段
m=i;
}
if(sum<0)//重新开始,和归0,坐标移到下一位
{
sum=0;
num=i+1;
} }
cout<<k<<m<<endl;
如果不需要输出首尾位置只找最大字段和的话,那么只需要一个状态转移方程和一个循环找最大值就OK。
int maxSubSum = 0;
dp[1] = Max(a[1], 0);//初始,取决于a(1)的±
int i;
for (i = 2; i <= N; ++i)
dp[i] = Max(dp[i - 1] + a[i], 0);//状态方程
for (i = 1; i <= N; ++i) {//循环找最大
if (maxSubSum < dp[i])
maxSubSum = dp[i];
}
接下来整理了对我来说比较难理解的最大子矩阵和和最大m子段和的思想。最大子矩阵的思想:将二维转化一维,理解最大字段和的基础上,可以这样理解最大子矩阵和的问题。
举个栗子:
0 -2 -7 0
9 2 -6 2
-4 1 -4 1
-1 8 0 -2
(1)求矩阵大小是1★k//一行
可以发现就是求每行的最大连续子序和
0 -2 -7 0 (ans=0,矩阵为[0])
9 2 -6 2 (ans=11,矩阵为[9 2])
……
(2)求矩阵大小是2★k//两行
这时可以在第1,2行或2,3行或3,4行找最大矩阵,比如第一二行:
0 -2 -7 0
9 2 -6 2
最大矩阵是 0 9
因为是求和,不需要标记具体的位置,矩阵肯定是竖着一列都取的,不可能这一列取到第i个元素,上一列取到第i-1个元素。两行可以加起来
9 0 -13 2
这样求出的最大连续子序和是9,这个结果也就是这个矩阵对应的最大矩阵和。
(3)同理3★k和4★k,之后所有情况都清楚了。
通过i和j确定起始行和终止列,将其合并为一个一维序列,找最大子段和,这样就可以找到子矩阵了。实践的过程中来一个函数找出一行最大子段和,记得重置0。
void solve(int j){
memset(dp,0,sizeof(dp));
for(int i=1;i<=n;i++){
dp[i]=max(b[j][i],dp[i-1]+b[j][i]);
mx=max(mx,dp[i]);
}
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
scanf("%d",&a[i][j]);
}
}
for(int i=1;i<=n;i++){//从第i行开始加
memset(b,0,sizeof(b));//归零
for(int j=i;j<=n;j++){//加到j行
for(int k=1;k<=n;k++){//第j行各个列的值
b[j][k]=a[j][k]+b[j-1][k];
}
solve(j);
}
}
printf("%d\n",mx);
}
https://blog.csdn.net/mirocky/article/details/104163844?utm_source=app&app_version=4.5.8
最大m子段和只理解了还未整理。
脚踏实地,还需更努力,因为想要的不会凭空而来★