当前位置:首页 > 技术 > 正文内容

基于动态规划的排队方案计数问题解析

访客 技术 2026年7月17日 1

问题背景

在一场促销活动中,有 n 个人正在排队购买商品。每个人的身份分为两类:A类和B类,他们对排队位置有不同的偏好:

  • A类人员希望自己的位置不早于指定的序号 |a_i|
  • B类人员则希望自己的位置不晚于指定的序号 |a_i|

其中,若 a_i < 0 表示该人为A类,否则为B类。目标是计算满足所有约束条件的合法排队方式总数。

输入格式

第一行给出整数 n(表示人数),第二行为 n 个非零整数 a_1, a_2, ..., a_n,描述每个人的类型与要求。

样例分析

输入:
4
2 -2 -3 4

输出:
6

解释:共有4人,其中-2和-3为A类(必须在第2、第3位或更后),2和4为B类(必须在第2、第4位或更前)。满足条件的排列共6种。

解法思路

本题可通过动态规划结合组合数学求解。关键在于将不同类型的限制分别处理,并利用预处理阶乘优化排列数计算。

状态定义

dp[i][j] 表示处理完前 i 个位置时,已有 j 名A类人员被安排的方案数。

分类转移

  1. A类人员(延迟出现):对于要求不早于位置 k 的A类个体,在遍历到位置 i = k 时才开始允许其进入候选集合。使用累计计数器 s1 统计当前可选的A类人数。
  2. B类人员(提前限制):对于要求不晚于位置 k 的B类个体,必须确保其在位置 k 前完成分配。我们通过"延后校验"策略,在到达位置 i 时检查所有满足 |a_j| = i 的B类个体是否仍能安置。

组合数修正

当处理到第 i 个位置时,若有 c 个B类人员要求在此或之前完成入队,则需从剩余可用槽位中选出 c 个进行排列。设此时已放置 j 个A类人员,且此前已有 s2 个B类人员被安排,则剩余可用于这些B类人员的位置数为 i - j - s2。因此需要乘上排列数:

\[ P(i - j - s2,\ c) = \frac{(i - j - s2)!}{(i - j - s2 - c)!} \]

初始化与滚动数组优化

初始状态为 dp[0][0] = 1,即0个位置安排0人仅有一种方式。由于状态只依赖前一层,采用滚动数组减少空间开销。

算法实现

预处理阶乘及其逆元以支持快速排列数查询,时间复杂度为 O(n²),符合题目规模限制。

代码框架

#include <bits/stdc++.h>
using namespace std;

const int N = 5005, MOD = 1e9 + 7;
int n, cntA[N], cntB[N], dp[2][N];
long long fac[N], invfac[N];

// 预处理阶乘与逆元
void initFac(int n) {
    fac[0] = 1;
    for (int i = 1; i <= n; ++i)
        fac[i] = fac[i-1] * i % MOD;
    invfac[n] = power(fac[n], MOD-2); // 快速幂求逆
    for (int i = n-1; i >= 0; --i)
        invfac[i] = invfac[i+1] * (i+1) % MOD;
}

long long perm(int n, int r) {
    if (r < 0 || r > n) return 0;
    return fac[n] * invfac[n - r] % MOD;
}

int main() {
    cin >> n;
    initFac(n);
    
    vector<int> a(n);
    for (int i = 0; i < n; ++i) {
        cin >> a[i];
        if (a[i] < 0) cntA[-a[i]]++;
        else cntB[a[i]]++;
    }

    dp[0][0] = 1;
    int cur = 0, totalA = 0, placedB = 0;

    for (int pos = 1; pos <= n; ++pos) {
        int nxt = cur ^ 1;
        memset(dp[nxt], 0, sizeof(dp[nxt]));
        totalA += cntA[pos];  // 新增可在pos及之后加入的A类人数

        for (int j = 0; j <= pos; ++j) {
            if (!dp[cur][j]) continue;

            // 放置一个A类人员
            if (j < totalA) {
                dp[nxt][j+1] = (dp[nxt][j+1] + 1LL * dp[cur][j] * (totalA - j)) % MOD;
            }

            // 不放A类,继续保留当前数量
            dp[nxt][j] = (dp[nxt][j] + dp[cur][j]) % MOD;
        }

        // 处理必须在pos前完成安排的B类人员
        if (cntB[pos] > 0) {
            for (int j = 0; j <= pos; ++j) {
                int available = pos - j - placedB;
                long long ways = perm(available, cntB[pos]);
                dp[nxt][j] = ways * dp[nxt][j] % MOD;
            }
        }

        placedB += cntB[pos];
        cur ^= 1;
    }

    cout << dp[cur][totalA] << endl;
    return 0;
}

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。