【NOIP 2007】矩阵取数游戏(区间DP)

一开始思路走偏 一直在想肯定与第几次取数有关 然后做了半天没做出来

看了题解才恍然大悟 原来有个性质“可以每行分开来做 最后加起来就好了”

那就变成了一个区间DP 我们设dp[i][j]表示从i到j闭区间的最大价值。(只考虑当前行)

那么dp[i][j]肯定是从i的右边/j的左边转移来的 则有dp[st][ed] = max( dp[st][ed-1] + num[ed] * 2^(m-len+1) , dp[st+1][ed] + num[st] * 2^(m-len+1));

然后答案就是dp[1][m] 最后把所有答案相加就好

多说一句 一般来说区间DP的写法都是先枚举区间长度 随后枚举起点

都8012年了 谁还会让你去写高精 果断__int128

// luogu-judger-enable-o2
#include<bits/stdc++.h>
#define ll __int128
#define N 85 
using namespace std;
int n,m,num[N];
ll bit[N],dp[N][N],ans; //只用关心每一行  
void write(ll x)
{
    if(x==0) return;
    else if(x) write(x/10);
    putchar(x%10+'0');
}
inline void Solve()
{
    memset(dp,0,sizeof(dp));
    for(int i=1;i<=m;i++)   dp[i][i]=num[i]*bit[m];
    for(int len=2;len<=m;len++)
    {
        for(int st=1;st+len-1<=m;st++)
        {
            const int ed=st+len-1;
            dp[st][ed]=max(dp[st][ed-1]+num[ed]*bit[m-len+1],dp[st+1][ed]+num[st]*bit[m-len+1]);
        }
    }
}
int main()
{
    cin>>n>>m;
    bit[0]=1;
    for(int i=1;i<=m;i++)   bit[i]=bit[i-1]*2;
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
        {
            cin>>num[j];
        }
        Solve();
        ans+=dp[1][m];
    }
    if(ans==0)  cout<<"0"<<endl;
    else write(ans);
    return 0;
}

转载于:https://www.cnblogs.com/Patrickpwq/articles/9824410.html

<think>好的,我现在需要帮助用户解决关于C++实现NOIP2007矩阵游戏的动态规划算法的问题。首先,我得回顾一下这个问题和相关的引用内容。 用户的问题是要找一个用C++实现的动态规划算法来计算矩阵游戏的最大得分。根据提供的引用,特别是引用3、4、5,问题涉及每行,每次从行首或行尾,得分是元素值乘以次i,总得分为各行的得分之和。动态规划是关键方法。 首先,我需要确认问题的具体要求。根据引用3,游戏规则是每次从每行一个元素,m次后完。每行的得分是该元素的值乘以的次i(从1开始),总得分是各行得分之和。因此,每行的处理是独立的,可以将问题分解为对每一行单独求解最大得分,然后将所有行的结果相加。这一点在引用5中也提到,二维矩阵可以拆分成一维列各自处理。 接下来,动态规划的设计。对于单行来说,如何设计状态转移方程是关键。根据引用2,动态规划用来存储和计算每一步的最大得分,避免重复计算。引用5提到可以使用维护左右端点的方法。对于一维组,通常的状态定义是dp[i][j]表示在区间[i,j]上能获得的最大得分。每次操作有两种选择:左端点或右端点。由于的次会影响得分,需要记录当前是第几次。不过,这里有个优化点:在区间长度确定的情况下,的次其实是确定的。例如,区间长度为m时,第一次,之后长度减少1,次增加1。或者可以反过来考虑,假设当前剩下的区间是[i,j],已经了k次,那么当前的元素会被乘以(m - (j - i)),因为总共有m次,剩下的次是j - i +1次,所以当前的是第k = m - (j - i)次。不过这个可能需要更仔细的分析。 或者,另一种思路是考虑每一步时,选择左或右,此时该元素被乘以当前的次t。例如,当处理到某个区间[i,j]时,已经了t-1次,现在第t次,所以元素a[i]或a[j]会被乘以t。然后,状态转移方程可以表示为: dp[i][j] = max(a[i] * 2^t + dp[i+1][j], a[j] * 2^t + dp[i][j-1]) 但是这里的问题在于t的值如何确定。因为每次的次是从1到m,所以当处理区间[i,j]时,已经了m - (j - i +1)次,因此当前的次是t = m - (j - i +1) +1 = m - (j - i)。例如,当区间长度为m时,第一次,t=1;当区间长度为1时,最后一次,t=m。这样,每次的乘是2^t,但根据题目描述,应该是元素值乘以t,而不是2^t。这里可能需要确认题目中的得分计算方式。根据引用3,得分是“每行的得分 = 被走的元素值 * i,其中i表示第i次”。所以正确的乘是i,即第几次,而不是2的幂次。但根据引用5中提到的高精度问题,可能原题中的得分计算方式不同。哦,这里可能需要仔细核对题目描述。 查看引用3的说明:“每行的得分 = 被走的元素值*i,其中i表示第i次(从1开始编号)”。所以确实是乘以次i,而不是指增长。那为什么引用5中提到使用__int128?这可能是因为在原来的NOIP2007题目中,得分计算是乘以2^i,导致值很大,需要高精度。但用户提供的引用3中的题目描述是弱化版,不含高精度,即乘为i。所以需要确认用户的问题是基于哪一个版本。根据用户的问题,他提到的是“NOIP2007 矩阵游戏”,而引用3是弱化版,不含高精度,而引用1、2、4、5可能涉及高精度。用户现在需要的是动态规划算法,可能不需要处理高精度,或者需要处理。但根据引用5,可以用__int128来处理高精度,而用户的问题可能希望得到的是标准的动态规划实现,可能假设值在合理范围内,或者使用__int128。 因此,我需要先明确问题的得分计算方式。根据用户的问题,他引用了多个来源,其中引用3说明的是弱化版,即乘以i,而其他引用可能涉及原题中的乘以2^i。例如,引用2中提到动态规划,可能原题的得分计算不同。这可能导致算法设计的差异。 需要仔细核对问题。根据用户的问题,他提到的是“NOIP2007 矩阵游戏”,而原题可能有两种版本。例如,Luogu P1005(引用4)的题目描述中,得分计算是否是乘以2^i?这可能影响算法设计。例如,原题NOIP2007提高组的题目中,得分计算可能为每次时,当前的是第t次,得分为元素值乘以2^t,然后总和。但用户提供的引用3中描述的是弱化版,得分是乘以i。因此,用户的问题需要明确是哪一个版本。但根据用户的问题描述,他说要寻找“C++实现NOIP2007矩阵游戏的最大得分动态规划算法”,而原题的正确得分计算方式需要确认。 但根据引用3中的描述,用户的问题中的得分计算是乘以i(即第几次),而原题可能不是这样。例如,查阅NOIP2007提高组的题目,矩阵游戏的得分规则是每次的得分为元素值乘以2^t,其中t是第几次。例如,第一次得分为a*2^1,第二次为b*2^2,依此类推。这可能使得值极大,需要高精度处理。但用户提供的引用3和5中,可能存在不同版本的题目。因此,用户的问题可能需要明确。但根据用户的问题中引用的内容,引用3是弱化版,不含高精度,而引用5提到可以用__int128处理。因此,可能存在两种不同的情况。 现在,用户的问题可能需要针对原题(可能涉及高精度或使用大)或者弱化版(直接乘以次i)。需要仔细分析引用中的内容。根据引用3的描述:“每次都有一个得分值,为每行的得分之和;每行的得分 = 被走的元素值*i,其中i表示第i次(从1开始编号)”,因此,用户的问题中的得分是乘以i,而不是2^i。而原题(引用4的Luogu P1005)可能要求的是每次得分乘以2^i,所以需要高精度。用户可能混淆了这两个版本。但根据用户的问题中的引用3,他可能需要弱化版的算法,即乘以i的情况。 因此,动态规划的设计应基于每行处理,每次时的得分为元素值乘以当前次t(从1到m)。对于每一行,我们需要计算的顺序使得总得分最大。由于每一行的处理独立,总得分是各行得分的总和。 对于单行的处理,假设该行有m个元素,我们需要进行m次,每次从两端选一个。动态规划的状态可以定义为dp[i][j]表示在剩余区间[i,j]时,还能k次的情况下获得的最大得分,其中k = j - i +1。因为当区间长度为len时,已经了m - len次,所以当前是第t = m - len +1次。例如,初始时,区间是[0, m-1](假设组从0开始),此时len=m,所以t=1,即第一次。当一个元素后,区间变为[i+1,j]或[i,j-1],此时len=m-1,t=2,依此类推。因此,每次的乘是t,等于m - (j - i) +1?或者需要更准确的计算。 例如,假设原行有m个元素,当前剩下的区间是i到j,长度为len = j -i +1。已经进行了m - len次,所以下一次是第t = m - len +1次。例如,当剩下m个元素时,已经进行了0次,接下来是第一次,t=1。当剩下一个元素时,已经进行了m-1次,接下来是第m次,t=m。因此,每次时的乘是t = m - (j - i) +1?或者更准确地说,t等于当前的次,即总次m减去剩下的元素加一? 是的,假设当前剩下的区间长度是 len = j -i +1,则已经了 m - len 次,所以当前的是第 (m - len +1)次。因此,对于区间[i,j],对应的乘是 t = m - (j -i +1) +1 = m - j +i -1 +1 = m - j +i = m - (j -i)。这可能更复杂。或者,直接计算t的值为 m - len +1,其中 len = j -i +1。例如: 当 len = m → t=1 当 len = m-1 → t=2 ... 当 len=1 → t=m 因此,在区间[i,j]时,t = m - (j-i+1) +1 = m -j +i -1 +1 = m -j +i → 这样可能有问题。或者,更简单的,当处理到区间[i,j]时,剩下的元素目是 len = j -i +1,已经进行了 (m - len),因此当前是第 (m - len +1),所以乘 t = m - len +1 = m - (j -i +1) +1 = m - j +i -1 +1 = m -j +i。这可能不直观。例如,假设m=3,初始区间是[0,2](len=3),此时t=1。一个元素后,剩下2元素,此时len=2,t=3-2+1=2。再一个元素,len=1,t=3-1+1=3。这样是对的。 因此,在动态规划的状态转移中,当处理区间[i,j]时,当前的乘是t = m - (j -i +1) +1 = m - (j-i) -1 +1 = m -j +i。所以,每次左端点的得分是 a[i] * t,加上处理剩下的区间[i+1,j]的得分;右端点的得分是 a[j] * t,加上处理剩下的区间[i,j-1]的得分。两者中的最大值。 因此,动态规划的状态转移方程可以表示为: dp[i][j] = max(a[i] * t + dp[i+1][j], a[j] * t + dp[i][j-1]) 其中,t = m - (j -i) (因为 j -i +1是当前区间长度,所以 t = m - (j -i +1) +1 = m -j +i) 或者更准确的计算: t = m - (j - i +1) +1 = m -j +i -1 +1 = m -j +i 例如,当i=0,j=0时,区间长度为1,t=m -0 +0 =m?这显然不对,因为当m=3,i=0,j=0时,此时是最后一次,即第3次,t应为3。而根据公式m-j+i=3-0+0=3,正确。当i=0,j=1,m=3时,t=3-1+0=2,正确,因为此时还剩两个元素,已经了3-2=1次,所以当前是第2次。所以公式正确。 因此,动态规划的状态转移方程正确。接下来,初始化条件。当区间长度为1时,即i ==j,此时只能该元素,此时t = m,因此dp[i][i] = a[i] * m。 对于区间长度从1到m,逐步处理更大的区间。然而,这种处理方式可能需要使用记忆化递归或者自底向上的动态规划。通常,自底向上的动态规划会按照区间长度从小到大进行处理。 例如,对于长度为1的区间,处理所有i=j的情况。然后处理长度为2的区间,依此类推,直到长度为m。 具体实现时,可以创建一个二维dp[n][n],其中n是每行的元素目。对于每行来说,n=m。假设每行存储为一个组arr,那么对于每个i和j,计算dp[i][j]。 接下来,对于每行的计算,总的最大得分是该行的dp[0][m-1]。 然后,将所有行的得分相加,得到总得分。 现在,考虑如何将这个思路转换为C++代码。例如,对于一个行组arr,长度为m,初始化一个二维dp[m][m]。 初始化时,对于所有i,dp[i][i] = arr[i] * m。 然后,对于长度l从2到m,遍历所有可能的i,其中j = i + l -1。对于每个i和j,计算当前的t = m - (j -i)(即m - (j-i+1) +1),然后左或右的最大值。 例如,对于l in 2..m: for (int l=2; l <=m; l++){ for (int i=0; i + l -1 <m; i++){ int j = i + l -1; int t = m - (j -i); // 或者等价于 t = m - (j -i +1) +1 ? int option1 = arr[i] * t + dp[i+1][j]; int option2 = arr[j] * t + dp[i][j-1]; dp[i][j] = max(option1, option2); } } 这样,当处理完所有长度后,dp[0][m-1]即为该行的最大得分。 然后,将每一行的这个值相加,得到总得分。 现在,测试这个逻辑是否正确。例如,假设一行有三个元素:a, b, c,m=3。初始时,dp[0][0]=a*3,dp[1][1]=b*3,dp[2][2]=c*3。 当处理长度2时,例如i=0,j=1,t=3 -1 =2。所以,option1是a*2 + dp[1][1](即a*2 +b*3),option2是b*2 + dp[0][0](即b*2 +a*3)。较大者。 同样,对于i=1,j=2,t=3 -1=2,计算option1是b*2 +c*3,option2是c*2 +b*3。 然后,处理长度3,即整个组。此时i=0,j=2,t=3 -2=1。所以,option1是a*1 + dp[1][2],option2是c*1 + dp[0][1]。其中,dp[1][2]是之前计算的b*2 +c*3和c*2 +b*3的最大值。假设这里较大的那个,比如假设b*2 +c*3更大,那么option1是a*1 +b*2 +c*3。而dp[0][1]是max(a*2 +b*3, b*2 +a*3)。假设a=1, b=2, c=3,则: dp[0][0] =1*3=3,dp[1][1]=2*3=6,dp[2][2]=3*3=9。 处理长度2: i=0,j=1: t=3-1=2. option1=1*2 +6=8. option2=2*2 +3=7. 所以 dp[0][1]=8. i=1,j=2: t=3-1=2. option1=2*2 +9=13. option2=3*2 +6=12 → dp[1][2]=13. 处理长度3: i=0,j=2: t=3-2=1. option1=1*1 +13=14. option2=3*1 +8=11 → dp[0][2]=14. 总得分14。但根据实际情况,顺序可能是: 第一次a(t=1 → a*1=1),第二次b(t=2 →2*2=4),第三次c(t=3 →3*3=9) →总和1+4+9=14。或者其他顺序是否可能更高? 例如,第一次c(t=1 →3*1=3),第二次a(t=2 →1*2=2),第三次b(t=3 →2*3=6) →总和3+2+6=11。或者第一次a,第二次c,第三次b →1*1 +3*2 +2*3=1+6+6=13。或者第一次c,第二次b,第三次a →3*1+2*2+1*3=3+4+3=10。或者第一次b,第二次a,第三次c →2*1 +1*2 +3*3=2+2+9=13。或者第一次b,第二次c,第三次a →2*1 +3*2 +1*3=2+6+3=11。所以最大值是14,确实等于dp的计算结果。所以算法正确。 这说明动态规划的逻辑是正确的。 接下来,将这个思路转化为C++代码。需要注意的是,对于每行单独处理,然后累加每行的最大得分。 假设输入的矩阵是n行m列的二维组。在C++中,可以逐行处理,计算每行的dp值,并累加到结果中。 代码的大致结构如下: 读n和m; 读矩阵,存储为二维组matrix; 初始化总得分sum=0; for 每行 in matrix: 创建dp[m][m]组; 初始化dp[i][i] = matrix[row][i] * m; 按区间长度从小到大填充dp组; sum += dp[0][m-1]; 输出sum; 需要注意的是,由于m可能较大(例如,原题中m可能到80),使用二维组可能会占用较多内存,但m=80时,dp组的大小是80x80=6400,这在C++中是可行的。 此外,关于据类型的选用,如果原题中的得分是乘以i,则值可能不会太大,使用int或long long即可。但如果原题中的得分是乘以2^i,则需要使用高精度或__int128。根据用户提供的引用5中提到可以使用__int128来避免高精度,因此,在代码中使用__int128可能更安全,尤其是在原题的情况下。但根据用户的问题中的引用3,弱化版可能不需要高精度,所以需要确认。用户的问题可能希望得到的是动态规划的实现,而据类型的选择可能需要根据题目要求。 假设用户需要的是原题的正确解法,而原题的得分计算是每次乘以2^t(t是),此时动态规划的状态转移方程中的乘应为2^t。例如,t=1时乘2,t=2时乘4,依此类推。这将导致非常大的值,需要高精度处理。例如,在引用5中提到可以用__int128来处理。然而,用户的问题中的引用3描述的是弱化版,乘为i,因此可能不需要处理大。用户的问题需要明确,但根据用户的问题,他可能希望得到原题的解法,即NOIP2007提高组的题目,这可能涉及到乘2^t的情况。 因此,可能存在两种不同的情况。现在需要确认用户的问题中的得分计算方式。根据引用3,用户的问题中的得分是乘以i,所以代码中乘是t。但根据引用5中的代码,原题中的得分是乘以2^t,所以需要用__int128。例如,在引用5的代码中,乘的是2的幂次,所以每个选择都需要乘以对应的2^t。因此,用户可能希望得到原题的正确解法,即得分是元素乘以2的t次方。 此时,我需要重新审视问题。用户引用的引用3描述的是弱化版,而其他引用可能指向原题。例如,引用4的Luogu P1005题目描述中的规则是否为乘以2^t? 查阅Luogu P1005的题目描述,确实,原题中的得分计算是每次的得分为元素值乘以2^t,其中t是第几次。例如,第一次乘2,第二次乘4,第三次乘8,依此类推。因此,得分计算方式与引用3中的弱化版不同。这种情况下,动态规划的状态转移方程中的乘应改为2^t,这会导致值迅速增大,需要处理大,但引用5提到可以用__int128来处理。 因此,用户的问题可能存在混淆,因为不同的引用对应不同的题目版本。用户的问题标题是“NOIP2007 矩阵游戏”,而原题的正确得分计算方式应为每次时乘以2^t,因此,动态规划的实现需要考虑这一点。 现在,我需要重新调整思路。对于原题的情况,每次的得分为元素乘以2^t,其中t是的次(从1开始),因此,每次的乘是2的幂次。例如,第一次时乘2,第二次乘4,依此类推。因此,状态转移方程中的乘应为2^t,而t的值由当前的次决定。 此时,动态规划的状态转移方程应为: dp[i][j] = max( a[i] * 2^t + dp[i+1][j], a[j] * 2^t + dp[i][j-1] ) 其中,t是当前的次。如前面所述,t等于m - (j-i+1) +1,即对于区间[i,j],剩下的长度是len=j-i+1,已经了m - len次,所以当前是第t = m - len +1次,乘是2^t。 例如,当m=3,区间长度为3时,t=1,乘2^1=2;当区间长度为2时,t=2,乘4;区间长度为1时,t=3,乘8。 因此,动态规划的状态转移方程需要计算乘2的幂次,这会导致值极大,必须使用高精度或大类型。但在引用5中提到,可以使用__int128来处理,因为其范围足够大(最大值为2^127-1),对于m=80,最大的乘是2^80,这会导致元素值乘以2^80,例如,假设元素值最大是1,则得分为2^80 ≈1e24,而__int128的最大值大约是1e38,足够处理m=80的情况。因此,使用__int128是可行的。 因此,正确的动态规划状态转移方程需要考虑2的幂次,并且使用__int128来存储得分。例如,在C++代码中: 定义dp[i][j]为区间[i,j]的最大得分,使用__int128类型。 初始化时,dp[i][i] = a[i] * 2^m(因为当区间长度为1时,当前是第m次,乘为2^m)。 但等一下,当区间长度为1时,剩下的元素目是1,所以已经了m-1次,当前是第m次,所以乘应为2^m。因此,初始化时,dp[i][i] = a[i] * 2^m? 这似乎有问题。例如,当m=3时,处理到最后一个元素时,乘应为2^3=8?是的。因为在原题中,第一次的乘是2^1,第二次是2^2,第三次是2^3。因此,当处理最后一个元素时,此时是第三次,乘是8。所以初始化时,每个单独元素的dp[i][i]的值是a[i] * 2^m? 不,这可能需要重新计算t的值。例如,对于区间长度为len,当前t = m - len +1。例如,当处理区间[i,j],其中len=1,t =m -1 +1 =m。所以乘是2^t=2^m。所以初始化时,dp[i][i] = a[i] * 2^m。例如,当m=3时,每个单独元素的得分是a[i] * 8。 但这样,当m=3时,整个行的最大得分是否会是正确的? 比如,考虑一个行元素a, b, c,按顺序a(第一次,乘2)、b(第二次,乘4)、c(第三次,乘8)的得分是a*2 +b*4 +c*8。或者另一种顺序,比如c、b、a,得分是c*2 +b*4 +a*8。或者a、c、b,得分是a*2 +c*4 +b*8。哪种顺序更大决于元素的值。动态规划需要找出最大的得分。 假设元素是1, 2, 3,m=3。则: - 顺序a→b→c:1*2 +2*4 +3*8=2+8+24=34. - 顺序c→b→a:3*2 +2*4 +1*8=6+8+8=22. - 顺序a→c→b:1*2 +3*4 +2*8=2+12+16=30. - 顺序b→a→c:2*2 +1*4 +3*8=4+4+24=32. - 顺序b→c→a:2*2 +3*4 +1*8=4+12+8=24. - 顺序c→a→b:3*2 +1*4 +2*8=6+4+16=26. 因此,最大得分是34。而动态规划的处理方式是否正确? 根据动态规划的状态转移方程,当处理整个区间[0,2]时,t=1,乘2^1=2。然后,左或右: option1 = a*2^1 + dp[1][2] option2 = c*2^1 + dp[0][1] 其中,dp[1][2]是处理区间[1,2]的最大得分,此时t=2,乘4。 处理区间[1,2]时,b或c: option1 =b*4 + dp[2][2] →b*4 +c*8. option2 =c*4 + dp[1][1] →c*4 +b*8. 当b=2,c=3时,option1=2*4 +3*8=8+24=32; option2=3*4 +2*8=12+16=28 →所以dp[1][2]=32. 同样,处理区间[0,1]时,t=2,乘4。a或b: option1 =a*4 + dp[1][1] →1*4 +2*8=4+16=20. option2 =b*4 + dp[0][0] →2*4 +1*8=8+8=16 →dp[0][1]=20. 所以,处理整个区间[0,2]时: option1=1*2 +32=34. option2=3*2 +20=6+20=26. 所以,dp[0][2]=34,与实际的最大得分一致。这说明动态规划的逻辑正确。 因此,正确的状态转移方程是考虑乘2^t,其中t = m - (j -i +1) +1 = m - len +1。因此,在代码中,我们需要计算2的t次方。但是,直接计算2^t可能会很耗时,特别是当t很大时。但观察发现,t的值范围是1到m,因此可以预处理2的幂次,存储在一个组pow2中,其中pow2[k] = 2^k。例如,pow2[0]=1,pow2[1]=2,pow2[m] = 2^m。这样,在计算时可以快速获对应的乘。 因此,在代码中,预处理一个pow2组,大小为m+1,存储2的幂次。例如: pow2[0] =1; for(int i=1; i<=m; i++){ pow2[i] = pow2[i-1] *2; } 然后,在状态转移中,t = m - (j -i +1) +1 = m - (j-i) -1 +1 = m - (j -i). 因此,当前乘为pow2[t] = pow2[m - (j -i +1) +1] = pow2[m - len +1],其中len=j-i+1。 或者更直接地,t = m - (j -i +1) +1 = m - len +1。例如,当处理区间[i,j],长度为len,则t = m - len +1,乘是pow2[t]。 因此,预处理pow2组,然后在动态规划过程中,计算t为m - (j -i +1) +1 = m - len +1,其中len = j -i +1。 现在,将所有这些转换为C++代码。需要注意的是,使用__int128来处理大,并且最后输出结果时,需要编写自定义的输出函,因为C++标准库不支持__int128的输入输出。 代码的大致结构如下: 预处理pow2组; 对于每行: 初始化dp组,其中dp[i][i] = arr[i] * pow2[m]; 按区间长度从小到大填充dp组; 总得分是所有行的dp[0][m-1]之和; 例如,具体代码: #include <iostream> using namespace std; typedef __int128 ll; const int MAXM = 85; ll pow2[MAXM]; // 存储2的幂次 ll dp[MAXM][MAXM]; void print(ll num) { if(num ==0) { cout<<0; return; } string s; while(num>0) { s += (num%10) + '0'; num /=10; } reverse(s.begin(), s.end()); cout << s; } int main() { int n, m; cin >> n >> m; // 预处理2的幂次 pow2[0] = 1; for(int i=1; i<=m; i++){ pow2[i] = pow2[i-1] *2; } ll sum =0; for(int row=0; row<n; row++){ int arr[m]; for(int i=0; i<m; i++){ cin >> arr[i]; } // 初始化dp组 for(int i=0; i<m; i++){ for(int j=0; j<m; j++){ dp[i][j] =0; } } for(int i=0; i<m; i++){ dp[i][i] = arr[i] * pow2[m]; // 初始时,每个元素单独时乘的是2^m? // 这里可能有问题,因为当区间长度为1时,t= m -1 +1 =m →乘2^m? // 是的,当区间长度为1时,此时是第m次,乘2^m. } // 处理长度从2到m for(int len=2; len<=m; len++){ for(int i=0; i + len -1 <m; i++){ int j = i + len -1; int t = m - len +1; // 因为 len = j-i+1 → t = m - (j-i+1) +1 = m - len +1 ll option1 = arr[i] * pow2[t] + dp[i+1][j]; ll option2 = arr[j] * pow2[t] + dp[i][j-1]; dp[i][j] = max(option1, option2); } } sum += dp[0][m-1]; } print(sum); return 0; } 但是这里有一个疑问:在初始化dp[i][i]时,是否是乘以pow2[m]?例如,当m=3,初始时,每个单独元素的得分是arr[i] * 2^3 =8*arr[i]。这在之前的例子中是如此,当处理最后一位元素时,乘的是8,而t=m=3。例如,当m=3,len=1,t=3-1+1=3,乘2^3=8。所以初始化是正确的。 但在之前的例子中,当处理整个行时,最大得分是34,而根据上述代码计算,当处理区间[0,2]时,t=1,乘2^1=2。然后,dp[0][2] = max( a*2 + dp[1][2], c*2 + dp[0][1} )。其中,dp[1][2}是max(2*4 +3*8=8+24=32, 3*4 +2*8=12+16=28) →32。而dp[0][1}=max(1*4 +2*8=4+16=20, 2*4 +1*8=8+8=16 →20). 所以,dp[0][2} = max(1*2 +32=34, 3*2 +20=6+20=26 →34). 因此,sum是34,而如果只有一行,总得分是34,与预期一致。这说明代码是正确的。 因此,上述代码能够正确处理原题中的情况,即每次乘2^t,并且使用__int128存储大。在输出时,需要自定义print函,因为标准输出不支持__int128。 用户的问题可能希望得到这样的代码。但需要根据用户的具体需求判断是否需要处理高精度或使用__int128。根据引用5,用户可能接受使用__int128的方案,因此这个代码是合适的。 总结,动态规划的关键点在于: 1. 每行独立处理。 2. 状态定义为区间[i,j]的最大得分。 3. 预处理2的幂次。 4. 使用自底向上的DP填充。 5. 使用__int128处理大。 最后,将各行的最大得分相加,得到总得分。</think>以下是C++实现的矩阵游戏动态规划算法,使用__int128处理大运算: ```cpp #include <iostream> #include <algorithm> using namespace std; typedef __int128 ll; const int MAXM = 85; ll dp[MAXM][MAXM], pow2[MAXM]; // __int128自定义输出函 void print(ll num) { if (!num) { cout << 0; return; } string s; while (num) { s += (num % 10) + '0'; num /= 10; } reverse(s.begin(), s.end()); cout << s; } int main() { int n, m; cin >> n >> m; // 预处理2的幂次 pow2[0] = 1; for (int i = 1; i <= m; i++) pow2[i] = pow2[i-1] * 2; ll total = 0; while (n--) { int arr[MAXM]; for (int i = 0; i < m; i++) cin >> arr[i]; // 初始化DP组 for (int i = 0; i < m; i++) dp[i][i] = arr[i] * pow2[m]; // 动态规划状态转移 for (int len = 2; len <= m; len++) { for (int i = 0; i + len - 1 < m; i++) { int j = i + len - 1; int t = m - len + 1; // 计算当前 ll left = arr[i] * pow2[t] + dp[i+1][j]; ll right = arr[j] * pow2[t] + dp[i][j-1]; dp[i][j] = max(left, right); } } total += dp[0][m-1]; } print(total); return 0; } ``` ### 算法解析 1. **预处理幂次**:预先计算$2^k$值存储于`pow2`组,避免重复计算[^5] 2. **状态定义**:`dp[i][j]`表示处理区间$[i,j]$时能获得的最大得分 3. **状态转移**:每次选择左端点或右端点,计算两种选择的最优解: $$dp[i][j] = \max(a[i] \times 2^{t} + dp[i+1][j],\ a[j] \times 2^{t} + dp[i][j-1])$$ 其中$t = m - (j-i+1) + 1$为当前[^2] 4. **初始条件**:当区间长度为1时,直接并乘以$2^m$[^3] ### 复杂度分析 - 时间复杂度:$O(nm^2)$,每行需要$O(m^2)$时间处理 - 空间复杂度:$O(m^2)$,使用二维组存储状态 ### 注意事项 1. 使用__int128处理大运算,需自定义输出函 2. 每行独立处理,总得分为各行最大值之和[^3] 3. 动态规划采用自底向上的递推方式,保证状态转移顺序正确
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值