BZOJ2302 [HAOI2011]Problem c

本文针对洛谷P2523 (BZOJ2302) 提供了一种有效的解决方案。首先判断是否存在解,接着通过状态转移方程 f[i][j] 计算可能的方案数,最终得出答案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Address


Solution

  • 先考虑无解的情况,我们记 s[i] s [ i ] 表示已经确定的 m m 人中编号 i 的人数。
  • 那么如果存在 s[i]>ni+1 s [ i ] > n − i + 1 ,显然无解。
  • 进一步的,对于有解的情况我们可以想到一个状态 f[i][j] f [ i ] [ j ] ,表示剩余 nm n − m 人中编号 i ≥ i 的人已经确定 j j 个人的编号的方案数,则:
    f[i][j]=k=0jf[i+1][jk]×Cjk(0jns[i]i+1)
  • 即表示在已经确定 jk j − k 人编号的情况下,再选择 k k 人确定编号为 i
  • 因为每个人都是互不相同的个体,所以对于任意一种方案,交换一些人的编号也算作不同的方案,因此还有乘上 Ckj C j k ,表示 j j 个人任选 k 人确定编号为 i i
  • 答案即为 f[1][nm],时间复杂度 O(Tn3) O ( T · n 3 )

Code

#include <iostream>
#include <cstdio>
#include <cstring>

using namespace std;

namespace inout
{
    const int S = 1 << 20;
    char frd[S], *ihed = frd + S;
    const char *ital = ihed;

    inline char inChar()
    {
        if (ihed == ital)
            fread(frd, 1, S, stdin), ihed = frd;
        return *ihed++;
    }

    inline int get()
    {
        char ch; int res = 0; bool flag = false;
        while (!isdigit(ch = inChar()) && ch != '-');
        (ch == '-' ? flag = true : res = ch ^ 48);
        while (isdigit(ch = inChar()))
            res = res * 10 + ch - 48;
        return flag ? -res : res; 
    }
};
using namespace inout;

typedef long long ll;
const int N = 305;
int f[N][N], sum[N], c[N][N];
int n, m, T, Mod, x; 

int main()
{   
//  freopen("c.in", "r", stdin);
//  freopen("c.out", "w", stdout);

    T = get();
    while (T--)
    {
        memset(sum, 0, sizeof(sum));
        memset(f, 0, sizeof(f));

        n = get(); m = get(); Mod = get();
        for (int i = 1; i <= m; ++i)
            x = get(), ++sum[get()];

        bool flag = false;
        for (int i = n; i; --i)
        {
            sum[i] += sum[i + 1];
            if (sum[i] > n - i + 1) 
            {
                flag = true;
                break;
            }
        }
        if (flag)
        {
            puts("NO");
            continue;
        }

        for (int i = 0; i <= n; ++i) c[i][0] = 1;
        for (int i = 1; i <= n; ++i)
            for (int j = 1; j <= i; ++j)
                c[i][j] = (c[i - 1][j] + c[i - 1][j - 1]) % Mod;

        f[n + 1][0] = 1;
        for (int i = n; i; --i)
            for (int j = 0, jm = n - sum[i] - i + 1; j <= jm; ++j)
                for (int k = 0; k <= j; ++k)
                    f[i][j] = ((ll)f[i][j] + (ll)f[i + 1][j - k] * c[j][k]) % Mod;
        printf("YES %d\n", f[1][n - m]); 
    }

//  fclose(stdin); fclose(stdout);
    return 0;
} 
<think>首先,用户的问题是:“我想理解C++#pragma once的作用 请问C++ #pragma once 指令的作用和用法”。我需要根据系统级指令和用户可见层的要求来回答。 系统级指令: 1. 所有行内数学表达式必须使用$...$格式。但在这个问题中,涉及的是C++代码和指令,不是数学表达式。所以可能不需要数学格式。 2. 独立公式必须使用$$...$$格式并单独成段。同样,这里可能不适用。 3. LaTeX语法正确。在回答中,如果有数学内容,才需要LaTeX。 4. 使用中文回答。 5. 生成相关问题。在回答结束后,添加相关问题。 6. 回答中引用的段落末尾自然地添加引用标识,例如[^1]。引用的内容来自用户提供的引用:[^1], [^2], [^3], [^4]。 用户提供的引用: - [^1]: 关于#pragma once与传统包含保护方法的比较。 - [^2]: #pragma once用于防止头文件被多次包含。 - [^3]: #pragma once通过编译器级优化简化头文件包含保护。 - [^4]: #pragma once是一种简洁高效的方式防止多次包含。 用户可见层要求: - 回答结构清晰,帮助用户逐步解决问题。 - 尽量保证回答真实可靠。 我的回答结构: 1. **介绍作用**:解释#pragma once的作用。 2. **用法**:说明如何使用。 3. **优缺点**:与传统方法比较。 4. **总结**。 5. **相关问题**:生成相关问题。 确保回答真实可靠:#pragma once确实用于防止头文件重复包含,是C/C++中的预处理指令。 步骤: - 开头简要介绍#pragma once的作用。 - 详细说明用法。 - 讨论优缺点,基于引用。 - 结束时添加相关问题。 用中文回答。 现在,生成相关问题: - 基于主题:C++#pragma once、头文件包含等。 - 例如: 1. #pragma once与传统包含保护方法有什么区别? 2. 在哪些情况下应该使用#pragma once? 3. #pragma once的兼容性问题如何解决? 4. 如何在大型项目中管理头文件包含? 参考引用:在回答中,当提到相关点时,添加引用标识。例如,当解释作用时,引用[^2];当比较传统方法时,引用[^1]。 最终回答结构: - 段落1:作用。 - 段落2:用法。 - 段落3:优缺点。 - 相关问题。</think>### C++ #pragma once 指令的作用和用法 在 C++ 开发中,`#pragma once` 是一个预处理指令,主要用于防止头文件在编译过程中被重复包含。这可以避免因重复包含导致的编译错误(如重复定义符号或类型),从而提升代码的可维护性和编译效率。该指令通过编译器级别的优化来实现这一功能,相比传统的宏保护方法(如 `#ifndef`、`#define`、`#endif`),它更简洁且易于管理[^2][^3]。 #### 作用详解 `#pragma once` 的核心作用是确保头文件在同一个编译单元中只被包含一次。当编译器遇到该指令时,它会记录该头文件的唯一标识(通常是文件路径),并在后续包含相同文件时自动跳过。这特别适用于大型项目,其中头文件可能被多个源文件引用,从而减少冗余代码和潜在冲突[^2][^4]。例如: - 如果头文件 `utils.h` 包含 `#pragma once`,那么无论源文件多次包含 `#utils.h`,编译器都只会处理一次。 - 这避免了传统方法中因宏定义冲突导致的错误,提高了代码的可靠性[^1][^3]。 #### 用法说明 `#pragma once` 的用法非常简单:只需在头文件的开头添加该指令即可。它不需要额外的宏定义或结束标记,因此代码更清晰。以下是一个标准示例: ```cpp // utils.h #pragma once // 确保此头文件只被包含一次 // 头文件内容 #include <vector> class Utility { public: void process(); }; ``` - **位置要求**:必须放在头文件的最顶部,在任何其他代码之前(包括注释)。这样编译器才能正确识别和处理。 - **兼容性**:该指令被大多数现代编译器支持(如 GCC、Clang、MSVC),但在一些老旧或特殊平台(如嵌入式系统)中可能不被识别。此时,可以结合传统方法作为备选: ```cpp #ifndef UTILS_H #define UTILS_H // 头文件内容 #endif ``` 这种混合使用方式能兼顾兼容性和效率[^3][^4]。 #### 优缺点分析 - **优点**: - **简洁性**:一行指令替代了传统方法的三行宏,减少了代码冗余,提高了可读性。 - **性能优化**:编译器内部处理更高效,避免了宏展开的开销,尤其在大项目中能加速编译过程。 - **错误预防**:自动处理文件唯一性,减少了人为错误(如宏名称拼写错误)。 - **缺点**: - **兼容性局限**:非标准 C++ 特性,部分编译器(如旧版 GCC 或某些嵌入式工具链)可能不支持,需要额外检查。 - **文件系统依赖**:依赖文件路径作为标识,如果文件被复制或重命名(但内容相同),可能导致保护失效。 总的来说,`#pragma once` 是现代 C++ 项目的首选方案,它能显著提升开发效率;但在跨平台或兼容性要求高的场景,建议结合传统方法使用[^1][^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值