Luogu P2789 直线交点数
核心思路
题目的关键在于,总交点数仅取决于这 \(N\) 条直线被分成了多少组互相平行的直线。
- 同一组内的直线互相平行,彼此没有交点。
 - 不同组的直线必定相交。
 
因此,问题可以转化为:将整数 \(N\) 分解为若干个正整数之和 \(N = n_1 + n_2 + \dots + n_k\)。这代表将 \(N\) 条直线分为 \(k\) 个平行线组,各组的直线数量分别为 \(n_1, n_2, \dots, n_k\)。
对于这样一种划分,总交点数等于任意两组直线之间的交点数之和,即 \(\sum_{1 \le i < j \le k} n_i \times n_j\)。
我们的目标就是找出所有可能的 \(N\) 的划分方案,计算出每种方案对应的总交点数,并统计有多少种不同的交点数。
算法实现
这个问题可以看作一个对整数 \(N\) 进行划分的搜索问题,非常适合使用深度优先搜索(DFS)解决。
我们设计一个递归函数 dfs(p, current_intersections),其参数含义如下:
p:当前还剩下p条直线等待分组。current_intersections:已经形成的平行线组之间产生的交点总数。
递归逻辑
递归出口
当 p == 0 时,表示所有 \(N\) 条直线都已经被分入某个平行组。此时 current_intersections 就是一个最终可能的交点总数。我们用一个布尔数组 f 记录这个交点数是否出现过。如果未出现,则将总方案数 ans 加一,并标记为已出现。
递归过程
在 dfs(p, current_intersections) 中,我们考虑从剩下的 p 条直线中,取 r 条(\(1 \le r \le p\))形成一个新的平行线组。这个新组(r 条直线)与之前已经分组的所有直线(共 \(N-p\) 条)都相交,产生 \(r \times (N-p)\) 个交点。
代码中的巧妙之处:代码中的状态转移 new_m = r * (p - r) + m 是一种等价的计算方式。它计算的是当前取出的 r 条直线与“未来将要分组”的 p-r 条直线之间的交点数。通过累加,最终结果是相同的。确定了 r 条直线作为新组后,问题规模缩小,我们继续处理剩下的 p-r 条直线。因此,我们递归调用 dfs(p - r, new_intersections)。
通过 for 循环遍历所有可能的 r 值(从 p 到 1),我们就能探索所有对 \(N\) 的划分方案。
代码解读
import sys
sys.setrecursionlimit(2000) # 增加递归深度限制
ans = 0  # 记录不同交点数的总数
f = [False] * 400 # 标记某个交点数是否已出现。N=25时,最大交点数为 C(25,2)=300,400足够
# p: 剩余未分组的直线数
# m: 已累计的交点数
def dfs(p, m):
    global ans
    # 递归出口:所有直线都已分组
    if p == 0:
        if not f[m]:  # 如果这个交点数是第一次出现
            ans += 1      # 方案数+1
            f[m] = True   # 标记为已出现
        return
    # 核心:从p条直线中,分出r条作为一组新的平行线
    for r in range(p, 0, -1):
        # 新产生的交点数 = 新分的组(r条) * 剩下未分的组(p-r条)
        # 加上之前已有的交点数m,得到新的累计交点数
        new_m = m + r * (p - r)
        
        # 递归处理剩下的 p-r 条直线
        dfs(p - r, new_m)
n = int(input())
dfs(n, 0) # 初始状态:n条直线,0个交点
print(ans)
总结
题解的本质是搜索整数 \(N\) 的所有划分,并对每种划分计算对应的交点数,最后统计不同交点数的个数。DFS 是实现这一过程的自然选择。

                
            
        
浙公网安备 33010602011771号