传纸条

传纸条

Time Limit: 1000 ms Memory Limit: 65536 KiB

Submit Statistic

Problem Description

传纸条是一种在课堂上传递信息的老方法,虽然现在手机短信和QQ聊天越来越普及,但是手写的信息会让人感到一种亲切感。对许多学生而言,在学校里传递一些私秘性的信息是一种令人兴奋的打发时光的方式,特别是在一些令人厌烦的课堂上。

 

XX 和 YY 经常在自习课的时候传纸条来传递一些私密性的信息。但是他们的座位相隔比较远,传纸条要通过其他人才能到达对方。在传递过程中,难免会有一些好奇心比较强的同学偷看纸条的内容。所以他们想到了一个办法,对纸条内容进行加密。

加密规则很简单:多次在信息的任意位置随意的添加两个相同的字母。

由于使用英文交流显得比较高端,所以他们的纸条内容只有英文。

现在给你加密后的信息,请你还原出原始的内容。

Input

 

输入数据的第一行为一个正整数 T(T ≤ 30),表示共有 T 组测试数据。

接下来 T 行,每行为一个字符串,字符串仅包含小写英文字母,且保证原始字符串中不包含相邻两个相同的字母,字符串长度不超过200000。

Output

 

每组数据输出一行字符串,表示还原后的内容。

Sample Input

1
ssilofaafveuuu

Sample Output

iloveu

Hint

样例解释:

删除掉aa后,又出现了ff,ff也要删除掉。

#include <bits/stdc++.h>

using namespace std;

int main()
{
    int n;
    scanf("%d", &n);
    while(n--)
    {
        char str[200001];
        char stk[200001];
        scanf("%s",str);
        int cnt = 0;
        stk[cnt++] = str[0];
        for(int i = 1; i < strlen(str); i++)
        {
            if(str[i] == stk[cnt-1])
            {
                cnt--;
            }
            else
            {
                stk[cnt++] = str[i];
            }
        }
        stk[cnt] = '\0';
        printf("%s\n",stk);
    }
    return 0;
}

 

<think>我们正在讨论“纸条”问题的动态规划解法,该问题通常涉及在一个网格中从左上角到右下角纸条,要求找出两条不相交的路径使得获得的数字总和最大。根据引用[1]和[2],我们使用四维动态规划数组dp[x1][y1][x2][y2],其中(x1,y1)表示第一条路径的位置,(x2,y2)表示第二条路径的位置,并且由于两条路径是同步进行的(即步数相同),所以有x1+y1=x2+y2。这样可以减少状态数量。 在引用[2]中提到了状态转移的四种情况(两个纸条各有两种移动方向:上或左,组合起来四种情况),并给出了状态转移方程的核心部分。 现在,我们需要用C++实现这个纸条问题的动态规划解法。 步骤: 1. 定义网格的大小(m行n列)和网格的值(假设用一个二维数组grid存储)。 2. 定义四维dp数组,维度为[m][n][m][n](注意:实际实现中可能因为空间太大而需要优化,但这里先按四维实现)。 3. 初始化:dp[0][0][0][0] = grid[0][0](因为两条路径都从(0,0)开始,但注意实际上两条路径不能重合,所以除了起点外,其他点不能重合。而起点是同一个点,我们这里先赋值为起点的值,然后在后续状态转移中注意跳过重合点?但题目要求路径不相交,所以我们需要避免在两个纸条走到同一个格子(除了起点和终点?实际上题目要求不相交,也就是说不能经过同一个格子,包括起点和终点?这里需要澄清)。通常,题目允许起点和终点重合,但中间路径不能相交。然而,在本题中,两条路径都是从(0,0)到(m-1,n-1),所以起点和终点都是同一个点。为了避免重复计算,我们可以在状态转移中排除走到同一个格子的情况(除了起点和终点)。但实际上,引用[1]中提到了两个人同步,所以步数相同,即x1+y1=x2+y2,因此我们可以用k=x1+y1=x2+y2作为步数,然后遍历x1,x2,则y1=k-x1, y2=k-x2。 为了避免四维数组空间过大(当m,n较大时,四维数组可能达到50^4=6250000,这是可以接受的,如果m,n<=50),但我们可以使用滚动数组优化。不过,我们先实现基础版本。 4. 状态转移方程: 对于每个步数k(从0到m+n-2),遍历x1(从0到min(k, m-1)),那么y1=k-x1(要求y1在[0,n-1]内),同样遍历x2(从0到min(k, m-1)),那么y2=k-x2(同样要求y2在[0,n-1]内)。 如果两条路径走到同一个格子(即x1==x2且y1==y2),那么我们不能重复计算该格子的数值(除了起点和终点?)。但是,题目要求路径不能相交,所以除了起点和终点,中间不能走到同一个格子。所以,在状态转移时,我们需要注意: - 当k=0时,起点,我们直接赋值grid[0][0](两条路径都在这里,但是题目允许起点,所以这里我们算一次)。 - 当k>0且k<m+n-2(即中间过程)时,如果两条路径走到同一个格子,那么这个状态是非法的,我们不应该考虑(或者跳过,或者将其设为负无穷表示非法)。 - 当k=m+n-2(终点)时,两条路径都到达终点,所以终点格子只能算一次?但实际上,题目要求两条路径,那么终点格子会被两条路径都走,所以应该算两次?但是题目要求的是纸条递,同一个格子上的数字只能被一个纸条获取。所以,两条路径除了起点和终点外,不能有交点。因此,在中间过程中,如果两条路径走到同一个格子,那么该状态无效。但是终点呢?终点我们允许两条路径都到达,但由于终点只有一个数字,所以实际上只能被计算一次。然而,在两条路径的终点状态,我们只能加一次终点的值。但是,我们两条路径从不同的方向到达终点,那么实际上我们需要避免重复加?所以,我们可以在终点状态时,如果两条路径到达同一个点,则只加一次。但是,起点也是同一个点,我们也是算了一次。 因此,我们可以在状态转移中这样处理:当(x1,y1)和(x2,y2)是同一个点时,该状态的值为grid[x1][y1]加上转移前的值(因为起点我们就是这么做的),而在中间过程中,我们不应该让两条路径走到同一个点(因为起点之后就不能再走到同一个点了)。所以,我们需要在状态转移中避免两条路径走到同一个点(除了起点)。 然而,在起点之后的每一步,我们都要保证两条路径不会走到同一个点。所以,在状态转移时,如果两条路径在同一个点(除了起点),我们就不更新这个状态(或者设为非法状态)。 但是,注意起点我们已经在初始化时设置了,那么对于k>=1的状态,我们遍历到的状态如果x1==x2且y1==y2,那么我们就跳过这个状态(即不进行状态转移,或者将该状态的值设为一个很小的数,这样不会影响最大值)。 5. 状态转移方程: 对于当前状态dp[x1][y1][x2][y2](其中k=x1+y1=x2+y2),它可以由以下四个状态转移而来: a. 第一条路径从上方来,第二条路径从上方来:dp[x1-1][y1][x2-1][y2] b. 第一条路径从上方来,第二条路径从左方来:dp[x1-1][y1][x2][y2-1] c. 第一条路径从左方来,第二条路径从上方来:dp[x1][y1-1][x2-1][y2] d. 第一条路径从左方来,第二条路径从左方来:dp[x1][y1-1][x2][y2-1] 然后,我们在这四个状态中取最大值,加上当前两个格子的数值。但是,要注意: - 如果两个格子是同一个(即(x1,y1)==(x2,y2)),那么只能加一次grid[x1][y1](因为同一个格子的数字只能被取一次)。 - 否则,加上grid[x1][y1] + grid[x2][y2]。 因此,状态转移方程可以写为: if (x1 == x2 && y1 == y2) dp[x1][y1][x2][y2] = max(tmp1, tmp2, tmp3, tmp4) + grid[x1][y1]; else dp[x1][y1][x2][y2] = max(tmp1, tmp2, tmp3, tmp4) + grid[x1][y1] + grid[x2][y2]; 但是,注意:在起点(0,0)处,我们已经初始化了,所以不需要转移。在k>=1时,我们进行转移。另外,在遍历时,要确保x1,y1,x2,y2在网格范围内。 6. 边界条件:在k=0时,只有dp[0][0][0][0]是有效的,值为grid[0][0](因为起点重合)。 7. 最终结果:dp[m-1][n-1][m-1][n-1],表示两条路径都到达终点的最大值(注意终点重合,所以我们也只能加一次终点的值)。 但是,注意:题目要求两条路径不能相交,我们的状态转移中通过跳过中间重合点(即k>=1时,如果两条路径重合则跳过)来避免。但是,我们在状态转移方程中已经处理了重合的情况(即当重合时只加一次值),这样会不会导致中间出现重合点?实际上,我们并没有跳过中间重合点的状态,而是直接计算了重合点的状态(但在重合点时只加一次值)。但是题目要求不能相交,所以重合点是不允许出现的(除了起点和终点)。因此,我们需要确保在中间过程中两条路径不能走到同一个点。所以,在状态转移时,如果k>=1且k<=m+n-3(即中间过程)时,我们不允许两条路径重合(即跳过重合状态)。但是,在起点(k=0)和终点(k=m+n-2)我们允许重合。 所以,在循环中,我们可以这样: for k from 1 to m+n-2: for x1 from max(0,k-n+1) to min(k, m-1): y1 = k - x1; for x2 from max(0,k-n+1) to min(k, m-1): y2 = k - x2; if (x1==x2 && y1==y2) { // 如果是中间过程,跳过(因为起点已经初始化了,所以在k>=1时,重合点都是不允许的,除了终点?) // 但终点是k=m+n-2,所以这里需要区分终点和中间过程。 // 实际上,当k=m+n-2时,只有终点一个状态,即x1=x2=m-1, y1=y2=n-1,这个点我们允许重合。 // 所以,在中间过程中(k>=1且k<=m+n-3),遇到重合点我们就跳过。 // 如果是终点,我们允许重合,并且只加一次值。 if (k != m+n-2) continue; } // 否则,进行状态转移 但是,这样处理会使得在终点时,我们只加一次值。但是,在起点我们已经在初始化中处理了。 然而,还有一种方法是:在状态转移过程中,我们允许重合点,但是在计算时,重合点只加一次值。然后,在中间过程中,如果两条路径重合了,那么这个状态的值可能是非法的(因为题目不允许中间重合),所以我们需要避免使用这个状态去更新后续状态?但是,我们的状态转移方程已经包括了这些状态,所以我们需要在状态转移时禁止中间重合点状态被更新(即不更新,或者设为负无穷,这样不会影响最大值)。因此,我们可以这样: if (x1==x2 && y1==y2) { if (k !=0 && k != m+n-2) { // 中间过程重合,非法,跳过(即不更新) // 或者我们可以设置dp[x1][y1][x2][y2]为一个很小的数,但这样可能会被后面的状态转移用到,所以最好不更新(保留初始值,初始值可以设为负无穷) continue; } } 所以,我们可以在初始化dp数组时,全部初始化为一个很小的数(比如-1e9),然后起点dp[0][0][0][0]=grid[0][0]。然后,在循环中,如果遇到中间过程重合,则跳过(不进行状态转移,这样该状态的值还是负无穷,就不会影响后续状态)。 8. 代码实现步骤: - 定义m,n - 定义网格grid - 定义dp数组:vector<vector<vector<vector<int>>>> dp(m, vector<vector<vector<int>>>(n, vector<vector<int>>(m, vector<int>(n, -1e9)))); - 初始化:dp[0][0][0][0] = grid[0][0]; - 枚举k从1到m+n-2(因为k=0已经初始化) for x1 in [0, min(k, m-1)]: y1 = k - x1; if (y1>=n) continue; // 或者x1的范围控制好:x1从max(0, k-n+1)到min(k, m-1) for x2 in [0, min(k, m-1)]: y2 = k - x2; if (y2>=n) continue; // 同上,控制范围避免越界 // 检查是否中间重合点(除了起点和终点) if (x1==x2 && y1==y2 && k!=m+n-2) { // 中间重合点,跳过 continue; } // 状态转移:四种前驱状态 int val = 0; // 如果当前两个点重合,那么只加一次grid[x1][y1](这个重合只可能发生在终点,因为中间重合点我们已经跳过了) if (x1==x2 && y1==y2) { val = grid[x1][y1]; } else { val = grid[x1][y1] + grid[x2][y2]; } int tmp = -1e9; // 情况1:第一条路径从上方来(x1-1>=0),第二条路径从上方来(x2-1>=0) if (x1-1>=0 && x2-1>=0) { tmp = max(tmp, dp[x1-1][y1][x2-1][y2]); } // 情况2:第一条路径从上方来(x1-1>=0),第二条路径从左方来(y2-1>=0) if (x1-1>=0 && y2-1>=0) { tmp = max(tmp, dp[x1-1][y1][x2][y2-1]); } // 情况3:第一条路径从左方来(y1-1>=0),第二条路径从上方来(x2-1>=0) if (y1-1>=0 && x2-1>=0) { tmp = max(tmp, dp[x1][y1-1][x2-1][y2]); } // 情况4:第一条路径从左方来(y1-1>=0),第二条路径从左方来(y2-1>=0) if (y1-1>=0 && y2-1>=0) { tmp = max(tmp, dp[x1][y1-1][x2][y2-1]); } if (tmp != -1e9) { dp[x1][y1][x2][y2] = max(dp[x1][y1][x2][y2], tmp + val); // 这里可能dp[x1][y1][x2][y2]已经初始化过(比如从另一种方式到达),所以取max?但是实际上每个状态只被计算一次?不,因为可能有多种方式到达同一个状态(同一个x1,y1,x2,y2),所以我们需要取最大值。由于我们初始化为负无穷,所以第一次遇到就会更新,后面再遇到就取最大值。 } // 如果tmp还是-1e9,说明没有前驱状态,那么当前状态不可达,我们就保持负无穷 - 最终结果:dp[m-1][n-1][m-1][n-1](注意终点重合,但我们在状态转移中已经处理了,终点重合只加一次grid[m-1][n-1]) 但是,这里有一个问题:两条路径的行走顺序?实际上,我们枚举的k是步数,每一步两条路径都走一步,所以是同步的。 9. 空间优化:四维数组空间复杂度为O(m*n*m*n),在m,n=50时,50^4=6250000,这是可以接受的(在C++中大约6250000个int,25MB左右)。但如果m,n更大,就需要优化。我们可以用滚动数组,因为k只依赖于k-1,所以我们可以用两个三维数组(去掉一维,因为k=x1+y1,所以用三维:dp[k][x1][x2])来保存状态。但这里我们先实现四维。 代码实现(注意:这里假设网格grid已经存储了数值,且非负,所以不可达状态用负无穷不会影响最大值) 注意:在状态转移时,我们可能会遇到同一个状态被多次更新(因为四种前驱状态),所以我们要取最大值。但因为我们初始化为负无穷,所以每次遇到更大的值就更新。 下面给出C++代码: 注意:我们假设网格grid的大小为m行n列。 </think>以下是使用C++实现"纸条"问题(动态规划解法)的完整代码和详细说明。该问题要求在一个网格中找到两条从左上角(0,0)到右下角(m-1,n-1)的路径,使路径上的数字和最大,且两条路径不能相交(起点和终点除外)。 ```cpp #include <iostream> #include <vector> #include <algorithm> #include <climits> using namespace std; int main() { // 网格尺寸和初始化(示例数据) int m = 3, n = 3; vector<vector<int>> grid = { {0, 3, 9}, {2, 8, 5}, {5, 7, 0} }; // 四维DP数组初始化 vector<vector<vector<vector<int>>>> dp( m, vector<vector<vector<int>>>( n, vector<vector<int>>( m, vector<int>(n, INT_MIN) ) ) ); // 起点初始化 dp[0][0][0][0] = grid[0][0]; // 动态规划:k为步数(x+y) for (int k = 1; k <= m + n - 2; ++k) { for (int x1 = max(0, k - n + 1); x1 <= min(k, m - 1); ++x1) { int y1 = k - x1; if (y1 >= n) continue; for (int x2 = max(0, k - n + 1); x2 <= min(k, m - 1); ++x2) { int y2 = k - x2; if (y2 >= n) continue; // 跳过中间重合点(除终点外) if (x1 == x2 && y1 == y2 && k != m + n - 2) continue; // 计算当前格子价值 int current_val = (x1 == x2 && y1 == y2) ? grid[x1][y1] : grid[x1][y1] + grid[x2][y2]; // 四种状态转移 int max_prev = INT_MIN; if (x1 > 0 && x2 > 0) max_prev = max(max_prev, dp[x1-1][y1][x2-1][y2]); // 上+上 if (x1 > 0 && y2 > 0) max_prev = max(max_prev, dp[x1-1][y1][x2][y2-1]); // 上+左 if (y1 > 0 && x2 > 0) max_prev = max(max_prev, dp[x1][y1-1][x2-1][y2]); // 左+上 if (y1 > 0 && y2 > 0) max_prev = max(max_prev, dp[x1][y1-1][x2][y2-1]); // 左+左 // 更新当前状态 if (max_prev != INT_MIN) { dp[x1][y1][x2][y2] = max(dp[x1][y1][x2][y2], max_prev + current_val); } } } } // 输出结果 cout << "最大价值: " << dp[m-1][n-1][m-1][n-1] << endl; return 0; } ``` ### 关键实现细节 1. **状态定义** 使用四维DP数组$dp[x1][y1][x2][y2]$,表示两条路径分别到达$(x1,y1)$和$(x2,y2)$时的最大价值[^1]。 2. **同步移动优化** 利用$k = x1 + y1 = x2 + y2$确保两条路径同步移动,减少无效状态[^1]。 3. **重合点处理** - 起点($k=0$): 强制重合,值为$grid[0][0]$ - 中间点($1 \leq k \leq m+n-3$): 禁止重合(`continue`跳过) - 终点($k=m+n-2$): 允许重合,值取$grid[m-1][n-1]$[^2] 4. **状态转移方程** 四种前驱状态取最大值: $$ \small dp[x1][y1][x2][y2] = \max\begin{cases} dp[x1-1][y1][x2-1][y2] \\ dp[x1-1][y1][x2][y2-1] \\ dp[x1][y1-1][x2-1][y2] \\ dp[x1][y1-1][x2][y2-1] \end{cases} + \begin{cases} grid[x1][y1] & \text{重合时} \\ grid[x1][y1] + grid[x2][y2] & \text{不重合时} \end{cases} $$ ### 复杂度分析 - **时间复杂度**:$O(m^2n^2)$ 四重循环($k$、$x1$、$x2$各$O(\max(m,n))$,总$O(m^2n^2)$) - **空间复杂度**:$O(m^2n^2)$ 四维DP数组开销 ### 优化方向 1. **滚动数组** 利用$k$只依赖$k-1$的特性,可将空间降至$O(m^2)$[^2] 2. **对称性剪枝** 利用路径对称性$x1 \leq x2$减少一半状态 3. **记忆化搜索** 避免无效状态计算,提高实际效率
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值