洛谷P1758 [NOI2009]管道取珠(dp 贡献转化)

题目

bzoj1566

两个管道的小球序列,

分别用长为n(n<=500)和长为m(m<=500)的仅由A和B构成的字符串表示

两个管道归并的时候,每次可以从上管道取一个球,也可以从下管道取一个球

假设最终可能产生的不同种类的输出序列共有K种,

其中第i种输出序列的产生方式(即不同的操作方式数目)有ai个。

聪明的小X早已知道,

因此,小X希望计算得到

你能帮助他计算这个值么?

由于这个值可能很大,因此只需要输出该值对1024523的取模即可(即除以1024523的余数)。

说明:文中C(n+m,n)表示组合数。组合数C(a,b)等价于在a个不同的物品中选取b个的选取方案数。

思路来源

https://www.luogu.com.cn/blog/user23116/solution-p1758 第一种dp

https://blog.youkuaiyun.com/pocket_lengend/article/details/79821748 第二种dp

题解

首先要理解一点,最终出现的每一个序列如果出现ai次,其贡献是ai^2

这等价于,对一个序列取两次,取两次的过程中出现两个序列相同的方案数,

设某个序列出现了a次,则在第一次里出现了a次,第二次里也出现了a次,

则对于该序列来说,第一次=第二次的方案数为a*a次,即a^2次,

理解了这一点后,有两种dp方式,

一种是dp[i][j][k][l]表示第一次第一个管道取了i个,第一次第二个管道j个

第二次第一个管道取了k个,第二次第二个管道取了l个的完全相同序列(p,q)对的方案数

把第四维优化掉就是dp[i][j][k],再把第一维滚动一下得到dp[2][505][505]

另一种是考虑总长,i+j=k+l,

所以dp[len][j][k]表示总长为len,第一次第一个管道取了j个,第二次第一个管道取了k个

的完全相同序列(p,q)对的方案数,第二种更便于写转移和理解,虽然看似多了一个二倍的常数

代码

#include<bits/stdc++.h>
using namespace std;
const int N=505,mod=1024523;
int n,m,dp[2][N][N];
char s[N],t[N];
void add(int &x,int y){
    x+=y;
    x%=mod;
}
int main(){
    dp[0][0][0]=1;
    scanf("%d%d",&n,&m);
    scanf("%s%s",s+1,t+1);
    for(int i=0,f=0;i<n+m;++i,f^=1){
        for(int j=0;j<=min(i,n);++j){
            for(int k=0;k<=min(i,n);++k){
                if(s[j+1]==s[k+1])add(dp[f^1][j+1][k+1],dp[f][j][k]);
                if(s[j+1]==t[i+1-k])add(dp[f^1][j+1][k],dp[f][j][k]);
                if(t[i+1-j]==s[k+1])add(dp[f^1][j][k+1],dp[f][j][k]);
                if(t[i+1-j]==t[i+1-k])add(dp[f^1][j][k],dp[f][j][k]);
                dp[f][j][k]=0;
            }
        }
    }
    printf("%d\n",dp[(n+m)&1][n][n]);
    return 0;
}

<think>好的,用户需要查找洛谷P1173 NOI2016网格问的Python解决方案或代码实现。首先,我需要回忆一下这个目的具体要求。目大意是判断一个网格是否会被障碍物分成不连通的部分,这可能涉及到图论中的割点或割边判断。 接下来,我需要考虑如何用Python实现这个问的解法。这类目通常需要高效的算法,因为数据规模可能较大。Python在处理大数据时可能会遇到性能问,所以需要考虑优化方法。 首先,目中的网格可以看作是一个图,其中某些格子被障碍物占据。判断是否存在割点,即移除某个点后图是否不连通,这是关键。Tarjan算法通常用于寻找割点,但实现起来需要注意细节,尤其是用Python时递归深度可能受限,可能需要用迭代方式或者优化代码。 然后,我需要查阅是否有现有的Python解法。不过用户提到站内引用中的内容似乎与快速排序或其他问相关,可能不直接适用。但根据引用[2],用户提到了代码优化和UB(未定义行为)的问,这提示在编写Python代码时需要注意避免类似错误,比如确保所有分支都有返回值,避免无限循环等。 另外,用户可能需要的是具体的代码示例。考虑到Python在二维网格问中的处理,通常会用坐标转换、邻接表或矩阵来表示图结构。对于割点检测,可以使用Tarjan算法,维护每个节点的dfn(发现时间)和low(能回溯到的最早节点)值,判断是否存在子节点无法通过其他路径回到祖先节点。 不过,Python的递归深度限制可能对Tarjan算法产生影响,尤其是在大网格情况下。因此,可能需要用非递归的DFS实现,或者调整递归深度限制。但这也可能导致代码复杂度增加。 另外,目中的障碍物可能形成多个连通块,需要处理多个情况。比如,当障碍物数量较少时,可能直接判断是否形成包围圈;当障碍物较多时,需要更复杂的判断逻辑。 总结步骤可能包括: 1. 将网格转换为图结构,标记障碍物。 2. 使用Tarjan算法寻找割点。 3. 判断是否存在割点导致图不连通。 4. 处理特殊情况,如网格过小、障碍物数量不足等。 在编写Python代码时,需要注意数据结构的效率,比如使用集合或字典来快速访问节点,避免重复计算。此外,可能的优化包括剪枝、预处理等。 最后,根据用户提供的引用,虽然引用内容不直接相关,但需要注意代码的正确性和优化,避免出现未定义行为或逻辑错误。例如,确保所有函数都有正确的返回语句,避免因编译器优化导致的错误,这在Python中可能表现为逻辑错误而非运行错误。</think>关于洛谷 P1173 NOI2016 网格问的 Python 解法,该问属于图论中的连通性判断问,需要判断是否存在障碍物形成的割点导致网格不连通。以下是解思路和实现要点: --- ### **问核心分析** 目要求判断 $n \times m$ 网格中,给定 $c$ 个障碍物的位置后,是否会将网格分割为多个不连通区域。关键在于判断是否存在 **割点**(即移除该点会导致图不连通)或 **割边**。 --- ### **算法选择** 1. **Tarjan 算法**:用于寻找无向图的割点。若存在割点,则网格可能被分割。 2. **连通性检测**:需考虑障碍物分布对整体连通性的影响,特别是边界情况和稀疏障碍物场景[^1]。 --- ### **Python 实现步骤** #### 1. **坐标离散化与邻接表构建** 将网格坐标转换为图中的节点,并为每个非障碍物节点建立邻接关系: ```python def build_graph(n, m, obstacles): obstacles_set = set(obstacles) graph = defaultdict(list) directions = [(-1,0), (1,0), (0,-1), (0,1)] for x in range(n): for y in range(m): if (x, y) not in obstacles_set: for dx, dy in directions: nx, ny = x + dx, y + dy if 0 <= nx < n and 0 <= ny < m and (nx, ny) not in obstacles_set: graph[(x, y)].append((nx, ny)) return graph ``` #### 2. **Tarjan 算法实现割点检测** 通过深度优先搜索(DFS)计算每个节点的 `dfn`(发现时间)和 `low`(回溯值): ```python def find_cut_points(graph, start): visited = set() dfn, low = {}, {} parent = {} cut_points = set() time = 0 def tarjan(u): nonlocal time dfn[u] = low[u] = time time += 1 children = 0 for v in graph[u]: if v not in dfn: parent[v] = u children += 1 tarjan(v) low[u] = min(low[u], low[v]) if parent.get(u) is None and children > 1: cut_points.add(u) elif parent.get(u) is not None and low[v] >= dfn[u]: cut_points.add(u) elif v != parent.get(u): low[u] = min(low[u], dfn[v]) tarjan(start) return cut_points ``` #### 3. **特殊情况处理** - **极小网格**:如 $n \times m \leq 2$ 时无需判断。 - **障碍物数量不足**:若障碍物数量少于 2,则无法分割网格[^2]。 --- ### **完整代码框架** ```python from collections import defaultdict def main(): n, m, c = map(int, input().split()) obstacles = [tuple(map(int, input().split())) for _ in range(c)] if n * m - c < 2: print("No") return if c == 0: print("Yes" if n * m >= 2 else "No") return graph = build_graph(n, m, obstacles) nodes = list(graph.keys()) if not nodes: print("No") return cut_points = find_cut_points(graph, nodes[0]) print("Yes" if len(cut_points) > 0 else "No") if __name__ == "__main__": main() ``` --- ### **注意事项** 1. **性能优化**:Python 递归深度有限,对大规模网格需改用迭代式 DFS。 2. **边界条件**:注意坐标范围和障碍物去重。 3. **连通性验证**:需确保所有非障碍物节点均被访问,防止漏判。 --- 相关问
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小衣同学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值