写了一些dp

今天写了两道dp,dp给我的感觉就是很神奇的感觉,想是想不到的,抄是一定要抄的。

一道是除以三题目登录—专业IT笔试面试备考平台_牛客网,这道题考的是是否熟悉子序列的构造,刚好我就不熟悉,因为我dp也才刚刚起步。

int dp[4][N];//i表示余几,j表示选第几个数了
void solve(){
    string s;
    cin>>s;
    int len=s.length();
    int num=s[0]-'0';
    int x=num%3;
    dp[x][0]=1;
    for (int i = 1; i <len; i++) {
            num=s[i]-'0';
            if(num%3==0){
                dp[0][i]=dp[0][i-1]*2+1;
                dp[1][i]=dp[1][i-1]*2;
                dp[2][i]=dp[2][i-1]*2;
            }
            else if(num%3==1){
                dp[0][i]=dp[0][i-1]+dp[2][i-1];
                dp[1][i]=dp[1][i-1]+dp[0][i-1]+1;
                dp[2][i]=dp[1][i-1]+dp[2][i-1];
            }
            else{
                dp[0][i]=dp[0][i-1]+dp[1][i-1];
                dp[1][i]=dp[1][i-1]+dp[2][i-1];
                dp[2][i]=dp[2][i-1]+dp[0][i-1]+1;
            }
            dp[0][i]%=mod;
        dp[1][i]%=mod;
        dp[2][i]%=mod;
        //cout<<dp[0][i]<<" "<<dp[1][i]<<" "<<dp[2][i]<<endl;
    }
    cout<<dp[0][len-1]<<endl;
}

解题过程因为是看题解的,所以这里就不写了,希望有一天可以写出自己的题解

第二个是删括号

这个我一开始因为,欸,好像可以写,因为想到括号一定会删最右边的,没有证明,然后就错了,并且浪费了很多时间也想不到怎么做。一看题解,i,j指向两个字符串的不同位置,k表示左括号。说实话k的用法我可以想到,但没想到可以融入dp里,更没想到i,j指向的两个不同的字符串。

现在我发现i,j表示的东西真的五花八门,真的很考验思维和熟练度,不同于其他的模板题。模板写多了就成固定思维了,写写dp跳脱一下。

以下是代码

#include<bits/stdc++.h>
using namespace std;
int main(){
    string s,t;
    cin>>s>>t;
    int len1=s.length();
    int len2=t.length();
    bool dp[105][105][55];
    memset(dp,0,sizeof(dp));
    dp[0][0][0]=true;
    for(int q=0;q<len1;q++){
        for(int w=0;w<len2;w++){
            for(int e=0;e<len1/2;e++){
                if(dp[q][w][e]){
                    if(e==0&&s[q+1]==t[w+1]) dp[q+1][w+1][0]=true;
                    if(s[q+1]=='(') dp[q+1][w][e+1]=true;
                    else if(e) dp[q+1][w][e-1]=true;
                }
            }
        }
    }
    if(dp[len1][len2][0]) cout<<"Possible"<<endl;
    else    cout<<"Impossible"<<endl;
    return 0;
}

<think>我们使用动态规划,定义状态: 设 $dp[i]$ 表示前 $i$ 个气球的合法排列方案数。 但是,由于最后两个气球是否相同会影响下一个气球的选择,因此我们需要更细致的状态划分。 实际上,我们可以定义两个状态: - $a_i$:表示长度为 $i$ 且最后两个气球颜色不同的方案数。 - $b_i$:表示长度为 $i$ 且最后两个气球颜色相同的方案数。 那么,总方案数 $dp[i] = a_i + b_i$。 状态转移: 1. 对于 $a_i$(最后两个不同),那么最后一个气球可以选择任意颜色,只要与倒数第二个不同。而倒数第二个气球可以是任意情况(即前 i-1 个气球的任意合法排列,且注意在长度为 i-1 的排列中,最后一个气球(即原序列的第 i-1 个)可能是单独一个(即属于$a_{i-1}$)或者是和前面相同(即属于$b_{i-1}$))。但是,我们考虑在长度为 i 且最后两个不同的情况,可以由长度为 i-1 的任意方案(即 $a_{i-1}+b_{i-1}$)在末尾添加一个与最后一个气球颜色不同的气球得到。因此,有: $$a_i = (a_{i-1} + b_{i-1}) \times (k-1)$$ 2. 对于 $b_i$(最后两个相同),那么最后两个气球颜色相同,这意味着倒数第二个气球必须与倒数第三个气球不同(否则就连续三个相同了),所以只能由长度为 i-1 的排列中最后两个气球不同的情况(即$a_{i-1}$)在末尾添加一个与最后一个气球相同的颜色得到。因此,有: $$b_i = a_{i-1} \times 1$$(注意:这里乘以1是因为只能选择与最后一个气球相同的颜色,且只有一种选择) 但是注意:在 $a_{i-1}$ 中,最后两个气球是不同的,所以我们在末尾添加一个与倒数第二个气球相同的颜色?不对,实际上我们只能添加一个与最后一个气球(即第 i-1 个气球)相同的颜色。而 $a_{i-1}$ 表示的是长度为 i-1 且最后两个不同的排列,那么最后一个气球(第 i-1 个)与倒数第二个(第 i-2 个)不同。所以当我们添加第 i 个气球时,我们让它和第 i-1 个气球相同,这样第 i-1 和 i 相同,且第 i-2 和 i-1 不同,所以不会出现连续三个相同。 因此,状态转移方程为: $$ \begin{aligned} a_i &= (a_{i-1} + b_{i-1}) \times (k-1) \\ b_i &= a_{i-1} \end{aligned} $$ 初始条件: - 当 $i=1$ 时:$a_1$ 表示长度为1且最后两个不同?但长度为1时不存在最后两个,所以我们需要重新考虑初始条件。 实际上,长度为1时,我们可以认为: $a_1$:没有最后两个,我们可以认为它属于最后两个不同的情况,因为只有一个气球,所以方案数为 $k$(任意颜色)。 $b_1$:长度为1不可能有最后两个相同,所以 $b_1=0$。 - 当 $i=2$ 时: $a_2$:最后两个不同,第一个气球有 $k$ 种选择,第二个气球有 $k-1$ 种选择,所以 $a_2 = k*(k-1)$。 $b_2$:最后两个相同,第一个气球有 $k$ 种选择,第二个气球必须与第一个相同,所以 $b_2 = k$。 但我们可以用递推关系计算: $a_2 = (a_1+b_1)*(k-1) = (k+0)*(k-1) = k(k-1)$ $b_2 = a_1 = k$ 所以初始条件: $a_1 = k, b_1 = 0$ $a_2 = k*(k-1), b_2 = k$ 然后从 $i=3$ 开始递推。 但是,我们也可以将初始条件统一为 $i=1$ 开始,然后递推。 所以,我们可以这样: 设 a = k, b = 0 # i=1 的情况 然后从 i=2 到 n 循环: new_a = (a + b) * (k-1) new_b = a a, b = new_a, new_b 最后,总方案数为 a+b。 注意:当 n=1 时,直接返回 k。 另外,注意模运算。 但是,我们还可以进一步简化。将 $b_i = a_{i-1}$ 代入 $a_i$ 的表达式: $a_i = (a_{i-1} + b_{i-1}) \times (k-1) = (a_{i-1} + a_{i-2}) \times (k-1)$ 因为 $b_{i-1} = a_{i-2}$。 所以,我们可以只用一个状态 $a_i$ 表示,但是需要知道前两项。另外,总方案数 $dp_i = a_i + b_i = a_i + a_{i-1}$。 因此,我们可以定义: $dp_i$ 表示前 i 个气球的方案数。 则 $dp_i = a_i + a_{i-1}$ (其中 $a_i$ 是最后两个不同的方案数,$a_{i-1}$ 就是 $b_i$,即最后两个相同的方案数) 同时,由 $a_i = (a_{i-1} + a_{i-2}) \times (k-1)$,那么: $dp_i = a_i + a_{i-1} = (a_{i-1} + a_{i-2}) \times (k-1) + a_{i-1} = a_{i-1} \times k + a_{i-2} \times (k-1)$ 但是,我们也可以直接建立 $dp_i$ 的递推关系: 考虑第 i 个气球的颜色: - 如果第 i 个气球与第 i-1 个气球颜色不同,则第 i 个气球有 $k-1$ 种选择,而前 i-1 个气球的方案数为 $dp_{i-1}$。但是注意,在 $dp_{i-1}$ 中,最后两个气球可能相同也可能不同,而这里我们要求第 i 个气球与第 i-1 个不同,那么无论第 i-1 个气球前面是什么,第 i 个气球只要与第 i-1 个不同即可。所以这种情况的方案数为 $dp_{i-1} \times (k-1)$ 吗?不对,因为 $dp_{i-1}$ 包含了所有前 i-1 个的合法方案,但当我们添加一个与第 i-1 个不同的气球时,确实可以保证最后两个不同。然而,这里有一个问题:在 $dp_{i-1}$ 中,我们并没有区分最后两个是否相同。实际上,我们并不需要区分,因为只要第 i 个气球与第 i-1 个气球不同,那么不管前 i-1 个的末尾是什么,添加后都不会导致连续三个相同(因为最后两个不同)。所以这种情况的方案数为 $dp_{i-1} \times (k-1)$。 - 如果第 i 个气球与第 i-1 个气球相同,那么第 i-1 个气球必须与第 i-2 个气球不同(否则就连续三个相同)。因此,前 i-1 个气球的最后两个必须不同。而前 i-1 个气球中,最后两个不同的方案数实际上就是 $a_{i-1}$。而 $a_{i-1}$ 等于 $dp_{i-2} \times (k-1)$ 吗?注意,前 i-2 个气球的方案数为 $dp_{i-2}$,然后第 i-1 个气球与第 i-2 个气球不同(有 $k-1$ 种选择),所以 $a_{i-1} = dp_{i-2} \times (k-1)$。因此,在第 i 个气球与第 i-1 个相同的情况下,方案数为 $a_{i-1} \times 1 = dp_{i-2} \times (k-1)$。 所以总方案数: $dp_i = (dp_{i-1} \times (k-1)) + (dp_{i-2} \times (k-1))$ = $(k-1) \times (dp_{i-1} + dp_{i-2})$ 这样我们就得到了一个关于 $dp_i$ 的一维递推式。 但是,这个递推式是否正确?我们验证一下: 初始条件: $dp_0$:0个气球,方案数为1(空排列)?但题目要求排列n个气球,所以n>=1。我们考虑: $dp_1$:k $dp_2$:k*(k-1) + k = k^2 用递推式:$dp_2 = (k-1)*(dp_1+dp_0)$,那么 $dp_0$ 应该是多少? 如果取 $dp_0=1$(空排列),则 $dp_2 = (k-1)*(k+1) = k^2-1$,但实际应该是 $k^2$,所以不对。 因此,我们需要重新考虑初始条件。 我们重新定义: $dp_0$:没有气球,方案数为1(空排列)。 $dp_1$:k $dp_2$:k*(k-1) + k = k^2 那么,根据递推式:$dp_2 = (k-1)*(dp_1+dp_0) = (k-1)*(k+1) = k^2-1$,与 $k^2$ 不相等。 所以上面的推导可能有误。 我们再检查:在第二种情况(最后两个相同)时,我们得到方案数为 $dp_{i-2}*(k-1)$,但实际应该是:前 i-1 个气球的最后两个不同的方案数(即 $a_{i-1}$)等于 $dp_{i-1}$ 吗?不对,因为 $dp_{i-1}=a_{i-1}+b_{i-1}$,而 $b_{i-1}$ 是前 i-1 个中最后两个相同的方案数,所以 $a_{i-1} = dp_{i-1} - b_{i-1}$。但是,我们又知道 $b_{i-1}=a_{i-2}$,而 $a_{i-2}=dp_{i-2}-b_{i-2}$?这样太复杂。 因此,我们回到最初的两个状态:$a_i$ 和 $b_i$。 我们使用两个变量进行递推,空间复杂度是O(1),可以接受。 步骤: 初始化: if n==0: return 1? 但题目n>=1,所以不考虑0。 n=1: a1 = k, b1 = 0 -> total = k n=2: a2 = (a1+b1)*(k-1) = k*(k-1), b2 = a1 = k -> total = k*(k-1)+k = k^2 n>=3: a_i = (a_{i-1}+b_{i-1})*(k-1) b_i = a_{i-1} 因此,我们可以用两个变量迭代。 但是,题目要求一维动态规划,我们可以用一维数组,但也可以只用两个变量(因为只依赖前一个状态)。这里我们使用两个变量迭代,实际上是一维的(滚动数组)。 所以,我们采用两个变量 a 和 b 分别表示当前最后两个不同和相同的方案数。 算法: if n == 1: return k a, b = k*(k-1), k # n=2 的情况 # 注意:当n=2时,我们直接返回 a+b # 然后从3到n迭代 for i in range(3, n+1): new_a = (a + b) * (k-1) % MOD new_b = a % MOD a, b = new_a, new_b return (a+b) % MOD 但是,我们也可以从n=1开始迭代,统一处理: a, b = k, 0 # n=1 total = a + b # 如果n=1,直接返回total 然后从2到n: a, b = (a+b)*(k-1), a return (a+b) % MOD 验证n=2: 初始:a=k, b=0 迭代一次(i=2): new_a = (k+0)*(k-1) = k*(k-1) new_b = k total = k*(k-1)+k = k^2 验证n=3: 初始:a=k, b=0 i=2: a=k*(k-1), b=k i=3: new_a = (k*(k-1)+k)*(k-1) = (k^2)*(k-1) new_b = k*(k-1) total = k^2*(k-1) + k*(k-1) = k*(k-1)*(k+1) = k*(k^2-1) 例如,k=2, n=3: 2*(4-1)=6,与样例输入 n=3,k=2 输出6一致。 再验证n=2,k=3: 3*3=9,与样例输入2 3输出9一致。 所以这个递推正确。 因此,代码实现如下: MOD = 10**9+7 if n == 1: return k % MOD a, b = k, 0 # 初始为n=1的状态 # 然后从2到n迭代 for i in range(2, n+1): # 计算新的a和b new_a = (a + b) * (k-1) % MOD new_b = a % MOD a, b = new_a, new_b total = (a + b) % MOD return total 注意:当k=1时,如果n>=3,那么会出现什么? n=1: 返回1 n=2: a = (1+0)*0 = 0, b=1 -> total=0+1=1? 但实际上,k=1时,两个气球只能同色,所以方案数为1(两个1),但题目要求不能有超过两个连续,所以两个同色是允许的。 n=3: 第一次迭代(i=2)后:a=0, b=1 第二次迭代(i=3): a=(0+1)*0=0, b=0 -> total=0 所以n>=3时,k=1的方案数为0,符合要求(因为三个连续同色不允许)。 因此,代码可以处理k=1的情况。 但是,当k=0时,题目要求k>=1,所以不考虑。 另外,n=1时,返回k;n=0时,题目要求n>=1,所以不考虑。 所以,我们代码时不需要特别处理k=1,因为我们的递推在k=1时,当n>=3会得到0。 时间复杂度:O(n),空间复杂度:O(1)。 由于n最大10^5,所以O(n)可以接受。 最后,我们输出总方案数。 代码实现如下: </think> ### 一维动态规划解法 使用动态规划解决气球排列问题,定义两个状态变量: - $a$:表示以**不同颜色**结尾的排列方案数 - $b$:表示以**相同颜色**结尾的排列方案数 #### 状态转移方程 1. **不同颜色结尾的转移**: 当前气球颜色需与前一个不同,有 $(k-1)$ 种选择 $a_{\text{new}} = (a + b) \times (k-1)$ 2. **相同颜色结尾的转移**: 当前气球颜色需与前一个相同,且前一个状态必须是不同颜色结尾 $b_{\text{new}} = a$ #### 初始条件 - $n=1$ 时:$a = k$(任意颜色),$b = 0$(无连续) - 总方案数:$S = a + b$ #### 递推过程 从 $i=2$ 到 $n$ 迭代更新: ```math \begin{aligned} &a_{\text{new}} = (a + b) \times (k-1) \\ &b_{\text{new}} = a \\ &\text{更新 } a = a_{\text{new}}, b = b_{\text{new}} \end{aligned} ``` #### 边界处理 - $k=1$ 且 $n \geq 3$:方案数为 $0$(无法避免连续三个同色) - $n=1$:直接返回 $k$ #### 代码实现 ```python MOD = 10**9 + 7 def count_arrangements(n: int, k: int) -> int: if n == 1: return k % MOD a, b = k, 0 # 初始状态:n=1 for _ in range(2, n + 1): # 从第2个气球开始迭代 a, b = (a + b) * (k - 1) % MOD, a % MOD return (a + b) % MOD # 示例测试 print(count_arrangements(2, 3)) # 输出 9 print(count_arrangements(3, 2)) # 输出 6 print(count_arrangements(4, 3)) # 输出 66 ``` ### 算法分析 1. **时间复杂度**:$O(n)$ 只需 $n-1$ 次迭代,满足 $n \leq 10^5$ 的要求 2. **空间复杂度**:$O(1)$ 仅使用两个变量存储状态 3. **正确性验证**: - $n=2, k=3$:$3×3=9$ ✓ - $n=3, k=2$:$2×(2^2-1)=6$ ✓ - $k=1, n=3$:$0$ ✓(连续三个同色非法) ### 关键推导步骤 1. **状态定义**: 区分结尾状态避免连续三个同色 2. **转移逻辑**: - 不同颜色结尾 → 下一气球有 $(k-1)$ 选择 - 相同颜色结尾 → 必须由不同颜色结尾转移而来 3. **迭代更新**: 滚动更新变量节省空间[^5] --- ### 相关问题 1. 若要求不能有任何连续同色气球(即完全交替),如何修改动态规划状态? 2. 若气球颜色数量 $k$ 很大(如 $k \leq 10^9$),如何优化取模运算的效率? 3. 若允许最多三个连续同色气球,动态规划状态应如何扩展?[^3] 4. 如何输出所有合法的气球排列而不仅仅是计数? 5. 若不同颜色的气球有数量限制(非无限),如何调整动态规划方程?[^1]
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值