讲义

C++语言程序设计讲义

2022 csp-j 复赛真题

2024/10/13

# [CSP-J 2022] 乘方

# 题目描述

小文同学刚刚接触了信息学竞赛,有一天她遇到了这样一个题:给定正整数 aabb,求 aba^b 的值是多少。

aba^bbbaa 相乘的值,例如 232^3 即为 3322 相乘,结果为 2×2×2=82 \times 2 \times 2 = 8

“简单!”小文心想,同时很快就写出了一份程序,可是测试时却出现了错误。

小文很快意识到,她的程序里的变量都是 int 类型的。在大多数机器上,int 类型能表示的最大数为 23112^{31} - 1,因此只要计算结果超过这个数,她的程序就会出现错误。

由于小文刚刚学会编程,她担心使用 int 计算会出现问题。因此她希望你在 aba^b 的值超过 109{10}^9 时,输出一个 -1 进行警示,否则就输出正确的 aba^b 的值。

然而小文还是不知道怎么实现这份程序,因此她想请你帮忙。

# 输入格式

输入共一行,两个正整数 a,ba, b

# 输出格式

输出共一行,如果 aba^b 的值不超过 109{10}^9,则输出 aba^b 的值,否则输出 -1

# 样例 #1

# 样例输入 #1

10 9

# 样例输出 #1

1000000000

# 样例 #2

# 样例输入 #2

23333 66666

# 样例输出 #2

-1

# 提示

对于 10%10 \% 的数据,保证 b=1b = 1
对于 30%30 \% 的数据,保证 b2b \le 2
对于 60%60 \% 的数据,保证 b30b \le 30ab1018a^b \le {10}^{18}
对于 100%100 \% 的数据,保证 1a,b1091 \le a, b \le {10}^9

upd2022.11.14\text{upd 2022.11.14}:新增加一组 Hack\text{Hack} 数据。

# 解析

# 题目描述

给定两个整数 (a) 和 (b),要求计算 (a^b) 的值。如果结果大于 (10^9),则输出 (-1)。

# 解题思路

由于直接使用 pow 函数可能会出现溢出问题,导致结果变为负数,无法判断是否超过了 (10^9)。因此,我们可以模拟乘方运算的过程,通过逐次累乘来计算 (a^b):

  1. 每次将 (a) 累乘 (b) 次。如果在某次累乘时结果已经超过了 (10^9),则可以直接输出 (-1),并结束程序。
  2. 如果累乘完 (b) 次后结果未超过 (10^9),则输出最终的计算结果。

# 优化

对于不同的 (a) 值有一些特殊情况需要考虑:

  • 当 (a > 1) 时,累乘 (b) 次最多只需要进行 31 次循环(因为 (2^{31} \approx 10^9)),因此不会出现超时问题。
  • 但当 (a = 1) 时,累乘 (b) 次的结果始终为 1。此时,如果 (b) 特别大,可能会执行 (b) 次循环,最坏情况下会执行 (10^9) 次,导致超时。因此可以加一个特殊判断,当 (a = 1) 时,直接输出 1,这样可以避免超时风险。

# 代码实现

#include<bits/stdc++.h>
using namespace std;
long long a, b, c = 1;  // c 记录乘积

int main() {
    scanf("%lld%lld", &a, &b);  // 输入 a 和 b
    if (a == 1) {  // 特殊情况,a = 1 时直接输出 1
        cout << 1;
        return 0;
    }
    
    for (int i = 1; i <= b; i++) {  // 模拟累乘
        c *= a;  // 每次累乘 a
        if (c > (long long)1e9) {  // 如果结果超过 10^9,输出 -1
            printf("-1");
            return 0;
        }
    }
    
    printf("%lld", c);  // 最后输出累乘结果
}

# 代码解析

  1. 输入处理:使用 scanf 读取两个整数 (a) 和 (b)。
  2. 特殊判断 (a = 1):如果 (a = 1),则直接输出结果为 1,因为 (1^b = 1) 对任何 (b) 都成立。
  3. 循环累乘:对于 (a > 1),通过循环将 (a) 累乘 (b) 次。如果在累乘过程中发现结果已经大于 (10^9),则输出 (-1) 并提前结束程序。
  4. 输出结果:如果累乘结束且结果未超过 (10^9),则输出最终的乘积。

# 时间复杂度分析

  • 当 (a > 1) 时,最多进行 31 次累乘,因为 (2^{31} \approx 10^9)。这种情况下的时间复杂度为 (O(b))。
  • 当 (a = 1) 时,通过特判直接输出结果,避免执行过多次循环,因此没有超时风险。

# [CSP-J 2022] 解密

# 题目描述

给定一个正整数 kk,有 kk 次询问,每次给定三个正整数 ni,ei,din_i, e_i, d_i,求两个正整数 pi,qip_i, q_i,使 ni=pi×qin_i = p_i \times q_iei×di=(pi1)(qi1)+1e_i \times d_i = (p_i - 1)(q_i - 1) + 1

# 输入格式

第一行一个正整数 kk,表示有 kk 次询问。

接下来 kk 行,第 ii 行三个正整数 ni,di,ein_i, d_i, e_i

# 输出格式

输出 kk 行,每行两个正整数 pi,qip_i, q_i 表示答案。

为使输出统一,你应当保证 piqip_i \leq q_i

如果无解,请输出 NO

# 样例 #1

# 样例输入 #1

10
770 77 5
633 1 211
545 1 499
683 3 227
858 3 257
723 37 13
572 26 11
867 17 17
829 3 263
528 4 109

# 样例输出 #1

2 385
NO
NO
NO
11 78
3 241
2 286
NO
NO
6 88

# 提示

【样例 #2】

见附件中的 decode/decode2.indecode/decode2.ans

【样例 #3】

见附件中的 decode/decode3.indecode/decode3.ans

【样例 #4】

见附件中的 decode/decode4.indecode/decode4.ans

【数据范围】

以下记 m=ne×d+2m = n - e \times d + 2

保证对于 100%100\% 的数据,1k1051 \leq k \leq {10}^5,对于任意的 1ik1 \leq i \leq k1ni10181 \leq n_i \leq {10}^{18}1ei×di10181 \leq e_i \times d_i \leq {10}^{18}1m1091 \leq m \leq {10}^9

测试点编号 kk \leq nn \leq mm \leq 特殊性质
11 10310^3 10310^3 10310^3 保证有解
22 10310^3 10310^3 10310^3
33 10310^3 10910^9 6×1046\times 10^4 保证有解
44 10310^3 10910^9 6×1046\times 10^4
55 10310^3 10910^9 10910^9 保证有解
66 10310^3 10910^9 10910^9
77 10510^5 101810^{18} 10910^9 保证若有解则 p=qp=q
88 10510^5 101810^{18} 10910^9 保证有解
99 10510^5 101810^{18} 10910^9
1010 10510^5 101810^{18} 10910^9

# 解析

# 前置知识

本题的数学基础包括整式的加减乘除。我们需要运用这些基础知识来解题,例如完全平方和与完全平方差公式等。

# 思路

我们首先对给出的公式进行化简:

ed=(p1)(q1)+1ed = (p-1)(q-1) + 1

将其展开得:

ed=pqpq+1+1ed = pq - p - q + 1 + 1

化简后得到:

ed=npq+2ed = n - p - q + 2 (其中 n=pqn = pq)。

接着,将公式进一步整理得:

p+q=ned+2p + q = n - ed + 2

# 插句闲话

看到题目中的数据范围提示“以下记 m=ned+2m = n - ed + 2”时,我们可以发现这正是化简后的 p+qp + q,这说明我们的解法是正确的。

# 继续解题

现在我们已知以下两个等式:

{p+q=ned+2pq=n\begin{cases} p + q = n - ed + 2 \\ pq = n \end{cases}

需要求出 ppqq。我们知道,如果能求出 pqp - q,那么问题就迎刃而解了。这里,我们复习一下初中的完全平方和与完全平方差公式。

  • 完全平方和:((a + b)^2 = a^2 + 2ab + b^2)
  • 完全平方差:((a - b)^2 = a^2 - 2ab + b^2)

已知 ab=pqab = pqa+b=p+qa + b = p + q,我们可以通过这两个公式的差值来求出 ab=pqa - b = p - q

将完全平方和与完全平方差相减:

(a+b)2(ab)2=4ab(a+b)^2 - (a-b)^2 = 4ab

我们可以将其整理为:

(ab)2=(a+b)24ab(a-b)^2 = (a+b)^2 - 4ab

于是:

ab=±(a+b)24aba - b = \pm \sqrt{(a+b)^2 - 4ab}

代入已知的 a+b=p+qa+b = p+qab=pqab = pq 即可求出 pqp - q

# 补充解释

在这道题中,我们假设 pp 是较大的数,qq 是较小的数,因此 pqp - q 取正值。

n=pqn = pqp+q=ned+2p + q = n - ed + 2 代入可得:

{p+q=ned+2pq=(ned+2)24n\begin{cases} p + q = n - ed + 2 \\ p - q = \sqrt{(n - ed + 2)^2 - 4n} \end{cases}

我们将两个等式相加,得到:

2p=(ned+2)+(ned+2)24n2p = (n - ed + 2) + \sqrt{(n - ed + 2)^2 - 4n}

将等式两边同时除以 2,得到:

p=ned+2+(ned+2)24n2p = \frac{n - ed + 2 + \sqrt{(n - ed + 2)^2 - 4n}}{2}

再通过 p+q=ned+2p + q = n - ed + 2 求出:

q=(ned+2)pq = (n - ed + 2) - p

最终, ppqq 的表达式为:

{p=ned+2+(ned+2)24n2q=ned+2(ned+2)24n2\begin{cases} p = \frac{n - ed + 2 + \sqrt{(n - ed + 2)^2 - 4n}}{2} \\ q = \frac{n - ed + 2 - \sqrt{(n - ed + 2)^2 - 4n}}{2} \end{cases}

# 特殊情况处理

根据题目要求, ppqq 需要为正整数。如果平方根不是整数,说明 ppqq 的解不符合题意,需要进行特殊处理。具体来说,我们可以利用 long long 强制转换,舍弃小数部分,并检验是否符合题目中的方程。对于无法开平方的负数,sqrt 函数返回 NaN,此时我们同样处理为无效解。

# 代码实现

#include <iostream>
#include <cmath>
#include <algorithm>
using namespace std;

int main(int argc, const char * argv[]) {
    long long k;
    scanf("%lld", &k);
    while (k--) {
        long long n, e, d;
        scanf("%lld%lld%lld", &n, &e, &d);
        long long PsubQ = sqrt((n - e * d + 2) * (n - e * d + 2) - (n * 4));
        long long PaddQ = n - e * d + 2;
        long long P = (PsubQ + PaddQ) / 2;
        long long Q = PaddQ - P;
        if (P * Q == n && e * d == (P - 1) * (Q - 1) + 1 && P && Q) {
            printf("%lld %lld\n", min(P, Q), max(P, Q));
        } else {
            printf("NO\n");
        }
    }
    return 0;
}

# 代码解析

  1. 输入处理:每次读取三个数 nneedd
  2. 计算 p 和 q
    • 首先通过平方差公式计算 PsubQ=(ned+2)24nPsubQ = \sqrt{(n - ed + 2)^2 - 4n}
    • 然后计算 ppqq
  3. 验证条件
    • 判断 ppqq 是否满足 pq=npq = ned=(p1)(q1)+1ed = (p-1)(q-1) + 1
    • 如果条件满足,输出 ppqq 的较小和较大值,否则输出 “NO”。
  4. 复杂度分析:每次运算时间复杂度为 O(1)O(1),总时间复杂度为 O(k)O(k),其中 kk 为询问的次数。

# [CSP-J 2022] 逻辑表达式

# 题目描述

逻辑表达式是计算机科学中的重要概念和工具,包含逻辑值、逻辑运算、逻辑运算优先级等内容。

在一个逻辑表达式中,元素的值只有两种可能:00(表示假)和 11(表示真)。元素之间有多种可能的逻辑运算,本题中只需考虑如下两种:“与”(符号为 &)和“或”(符号为 |)。其运算规则如下:

0 \mathbin{\&} 0 = 0 \mathbin{\&} 1 = 1 \mathbin{\&} 0 = 0,1 \mathbin{\&} 1 = 1;
0 \mathbin{|} 0 = 0,0 \mathbin{|} 1 = 1 \mathbin{|} 0 = 1 \mathbin{|} 1 = 1。

在一个逻辑表达式中还可能有括号。规定在运算时,括号内的部分先运算;两种运算并列时,& 运算优先于 | 运算;同种运算并列时,从左向右运算。

比如,表达式 0|1&0 的运算顺序等同于 0|(1&0);表达式 0&1&0|1 的运算顺序等同于 ((0&1)&0)|1

此外,在 C++ 等语言的有些编译器中,对逻辑表达式的计算会采用一种“短路”的策略:在形如 a&b 的逻辑表达式中,会先计算 a 部分的值,如果 a=0a = 0,那么整个逻辑表达式的值就一定为 00,故无需再计算 b 部分的值;同理,在形如 a|b 的逻辑表达式中,会先计算 a 部分的值,如果 a=1a = 1,那么整个逻辑表达式的值就一定为 11,无需再计算 b 部分的值。

现在给你一个逻辑表达式,你需要计算出它的值,并且统计出在计算过程中,两种类型的“短路”各出现了多少次。需要注意的是,如果某处“短路”包含在更外层被“短路”的部分内则不被统计,如表达式 1|(0&1) 中,尽管 0&1 是一处“短路”,但由于外层的 1|(0&1) 本身就是一处“短路”,无需再计算 0&1 部分的值,因此不应当把这里的 0&1 计入一处“短路”。

# 输入格式

输入共一行,一个非空字符串 ss 表示待计算的逻辑表达式。

# 输出格式

输出共两行,第一行输出一个字符 01,表示这个逻辑表达式的值;第二行输出两个非负整数,分别表示计算上述逻辑表达式的过程中,形如 a&ba|b 的“短路”各出现了多少次。

# 样例 #1

# 样例输入 #1

0&(1|0)|(1|1|1&0)

# 样例输出 #1

1
1 2

# 样例 #2

# 样例输入 #2

(0|1&0|1|1|(1|1))&(0&1&(1|0)|0|1|0)&0

# 样例输出 #2

0
2 3

# 提示

【样例解释 #1】

该逻辑表达式的计算过程如下,每一行的注释表示上一行计算的过程:

0&(1|0)|(1|1|1&0)
=(0&(1|0))|((1|1)|(1&0)) //用括号标明计算顺序
=0|((1|1)|(1&0))   //先计算最左侧的 &,是一次形如 a&b 的“短路”
=0|(1|(1&0))       //再计算中间的 |,是一次形如 a|b 的“短路”
=0|1               //再计算中间的 |,是一次形如 a|b 的“短路”
=1

【样例 #3】

见附件中的 expr/expr3.inexpr/expr3.ans

【样例 #4】

见附件中的 expr/expr4.inexpr/expr4.ans

【数据范围】

s\lvert s \rvert 为字符串 ss 的长度。

对于所有数据,1s1061 \le \lvert s \rvert \le {10}^6。保证 ss 中仅含有字符 01&|() 且是一个符合规范的逻辑表达式。保证输入字符串的开头、中间和结尾均无额外的空格。保证 ss 中没有重复的括号嵌套(即没有形如 ((a)) 形式的子串,其中 a 是符合规范的逻辑表 达式)。

测试点编号 s\lvert s \rvert \le 特殊条件
121 \sim 2 33
343 \sim 4 55
55 20002000 1
66 20002000 2
77 20002000 3
8108 \sim 10 20002000
111211 \sim 12 106{10}^6 1
131413 \sim 14 106{10}^6 2
151715 \sim 17 106{10}^6 3
182018 \sim 20 106{10}^6

其中:
特殊性质 1 为:保证 ss 中没有字符 &
特殊性质 2 为:保证 ss 中没有字符 |
特殊性质 3 为:保证 ss 中没有字符 ()

【提示】

以下给出一个“符合规范的逻辑表达式”的形式化定义:

  • 字符串 01 是符合规范的;
  • 如果字符串 s 是符合规范的,且 s 不是形如 (t) 的字符串(其中 t 是符合规范的),那么字符串 (s) 也是符合规范的;
  • 如果字符串 ab 均是符合规范的,那么字符串 a&ba|b 均是符合规范的;
  • 所有符合规范的逻辑表达式均可由以上方法生成。

# 解析

# 题目背景

这道题的解法大多数人会选择通过表达式求值来解决,但实际上可以有更简单的做法:通过从左到右顺序扫描表达式,直接求解。

# 解题思路

主要逻辑:我们需要明确哪些部分的表达式不会对最终结果产生贡献,以及如何跳过这些部分。

  1. 跳过无效部分

    • 如果遇到 0&,则接下来的部分只有在出现 | 或与 0& 前面匹配的 ) 时,才会对结果产生贡献,且前面的部分结果为 0
    • 如果遇到 1|,则直到遇到 | 或与 1| 前面匹配的 ) 时,才会对结果产生贡献。
  2. 优先级问题

    • 事实上,运算符的优先级并不会影响结果。比如遇到 1| 时,它会直接跳到后面的括号,因此优先级实际上是隐含的。
    • 如果是 0|,它不会对结果有任何影响,& 运算同理。

# 举个例子:

对于表达式 0&(1|0)|(1|1|1&0)

  • 遇到 0&,则我们可以跳到 0&(1|0)|,因为第一个 | 在括号内,可以忽略,此时表达式变为 0|(1|1|1&0)
  • 由于 0| 不会对结果产生贡献,表达式简化为 (1|1|1&0),然后再从括号开始计算。

# 代码解析

以下是该思路的代码实现:

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

bool st;  // 暂时未使用的变量,可能用于调试
string str;  // 输入的表达式字符串
bool val;  // 表达式的最终值
int ans1, ans2, off;  // ans1 记录跳过的 & 操作数,ans2 记录跳过的 | 操作数,off 用于判断是否跳过 0& 或 1|,0 表示不用跳过
bool ed;  // 暂时未使用的变量,可能用于调试

int main(){
    ios::sync_with_stdio(false);  // 优化输入输出速度
    cin.tie(nullptr);  // 解除 cin 和 cout 的绑定
    cout.tie(nullptr);  // 解除 cin 和 cout 的绑定

    cin >> str;  // 输入表达式字符串

    for(int i = 0; i < str.size(); i++){
        if (off) {  // 如果需要跳过某些部分
            if (str[i] == '(') {  // 跳过括号内部的内容
                int x = 1;
                while (x) {
                    i++;
                    if (str[i] == '(') x++;  // 遇到 '(' 增加计数
                    if (str[i] == ')') x--;  // 遇到 ')' 减少计数
                }
            } else if (off == 1 && str[i] == '|') {
                off = 0;  // 遇到 '|' 时结束跳过
            } else if (str[i] == ')') {
                off = 0;  // 遇到括号结束
            } else if (off == 1 && str[i] == '&') {
                ans1++;  // 记录多余的 & 运算符
            } else if (off == 2 && str[i] == '|') {
                ans2++;  // 记录多余的 | 运算符
            }
        } else {  // 正常计算
            if (str[i] == '1') val = 1;  // 当前值为 1
            if (str[i] == '0') val = 0;  // 当前值为 0
            if (str[i] == '&' && val == 0) {  // 如果遇到 '0&',则后面部分不会产生贡献
                off = 1;  // 标记跳过
                ans1++;  // 记录跳过的 & 次数
            }
            if (str[i] == '|' && val == 1) {  // 如果遇到 '1|',则后面部分不会产生贡献
                off = 2;  // 标记跳过
                ans2++;  // 记录跳过的 | 次数
            } 
        }
    }

    cout << val << endl << ans1 << ' ' << ans2 << endl;  // 输出表达式结果及跳过的 & 和 | 次数

    return 0;
}

# 代码讲解

  1. 输入处理:首先读取表达式的输入字符串 str

  2. 核心逻辑

    • 跳过部分表达式:通过变量 off 控制是否跳过部分运算符。如果遇到 0&,则接下来的部分直到出现 | 或与其匹配的 ) 才会对结果产生影响;如果遇到 1|,则类似地跳过部分直到匹配。
    • 运算符处理:若 off == 1 且当前字符是 &,或者 off == 2 且当前字符是 |,则分别记录跳过的 &| 次数。
  3. 结果输出:输出最终表达式的值 val,以及跳过的 & 运算符次数 ans1| 运算符次数 ans2

# 总结

这种解法通过顺序扫描表达式并跳过无效部分,避免了复杂的表达式求值过程,达到了简化计算的目的。

# [CSP-J 2022] 上升点列

# 题目描述

在一个二维平面内,给定 nn 个整数点 (xi,yi)(x_i, y_i),此外你还可以自由添加 kk 个整数点。

你在自由添加 kk 个点后,还需要从 n+kn + k 个点中选出若干个整数点并组成一个序列,使得序列中任意相邻两点间的欧几里得距离恰好为 11 而且横坐标、纵坐标值均单调不减,即 xi+1xi=1,yi+1=yix_{i+1} - x_i = 1, y_{i+1} = y_iyi+1yi=1,xi+1=xiy_{i+1} - y_i = 1, x_{i+1} = x_i。请给出满足条件的序列的最大长度。

# 输入格式

第一行两个正整数 n,kn, k 分别表示给定的整点个数、可自由添加的整点个数。

接下来 nn 行,第 ii 行两个正整数 xi,yix_i, y_i 表示给定的第 ii 个点的横纵坐标。

# 输出格式

输出一个整数表示满足要求的序列的最大长度。

# 样例 #1

# 样例输入 #1

8 2
3 1
3 2
3 3
3 6
1 2
2 2
5 5
5 3

# 样例输出 #1

8

# 样例 #2

# 样例输入 #2

4 100
10 10
15 25
20 20
30 30

# 样例输出 #2

103

# 提示

【样例 #3】

见附件中的 point/point3.inpoint/point3.ans

第三个样例满足 k=0k = 0

【样例 #4】

见附件中的 point/point4.inpoint/point4.ans

【数据范围】

保证对于所有数据满足:1n5001 \leq n \leq 5000k1000 \leq k \leq 100。对于所有给定的整点,其横纵坐标 1xi,yi1091 \leq x_i, y_i \leq {10}^9,且保证所有给定的点互不重合。对于自由添加的整点,其横纵坐标不受限制。

测试点编号 nn \leq kk \leq xi,yix_i,y_i \leq
121 \sim 2 1010 00 1010
343 \sim 4 1010 100100 100100
575 \sim 7 500500 00 100100
8108 \sim 10 500500 00 109{10}^9
111511 \sim 15 500500 100100 100100
162016 \sim 20 500500 100100 109{10}^9

# 解析

# 题目概述

在一个二维平面内,给定 nn 个整数点 ((x_i, y_i)),此外还可以自由添加 kk 个整数点。添加这 kk 个点后,需要从 n+kn+k 个点中选出若干个点,组成一个序列,要求满足以下条件:

  1. 序列中任意相邻两点间的欧几里得距离恰好为 1;
  2. 序列中的点,横坐标 xx 和纵坐标 yy 必须单调不减,即:
    • xi+1xi=1x_{i+1} - x_i = 1yi+1=yiy_{i+1} = y_i,或
    • yi+1yi=1y_{i+1} - y_i = 1xi+1=xix_{i+1} = x_i

要求输出满足上述条件的序列的最大长度。

# 约束条件

  • n500n \leq 500
  • k100k \leq 100
  • xi,yi109x_i, y_i \leq 10^9

# 思路概述

这个问题看起来像是二维平面的最长不下降子序列问题,因此我们可以考虑使用动态规划(DP)来解决。由于 nnkk 的范围较小,我们可以使用时间复杂度为 O(n2k)O(n^2k) 的三次方算法来解决。

首先,对输入的点按 xx 为第一关键字、yy 为第二关键字进行排序,这样便于我们进行状态的转移。

# 动态规划状态定义

设状态 f[i][j]f[i][j] 表示枚举到第 ii 个点,还剩余 jj 个自由点可添加,此时满足题意的序列的最大长度。

# 状态转移方程

根据题意,我们可以得到以下状态转移方程:

f[i][j]=max(f[k][j+d]+d+1)f[i][j] = \max(f[k][j+d] + d + 1)

其中 k[1,i1]k \in [1, i-1],并且 d=xixk+yiyk1d = |x_i - x_k| + |y_i - y_k| - 1,表示从点 kk 到点 ii 之间需要添加多少个自由点才能使得距离符合条件。如果 j+d>kj + d > k,则不能进行状态转移。

# 最终答案

最终答案为 max(f[i][j]+j)\max(f[i][j] + j),其中 j[0,k]j \in [0, k],表示在选择某个状态时可能还剩余一些自由点,这些自由点可以直接用来延长序列的长度。

# 复杂度分析

动态规划的时间复杂度为 O(n2k)O(n^2 k),由于 n500n \leq 500k100k \leq 100,这个复杂度是可以接受的。

# 代码实现

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

const int N=510,K=110;

int n, k;
struct node{
    int x, y;
    bool operator< (const node &w) const {
        if(x == w.x) return y < w.y;
        return x < w.x;
        // 运算符重载,以x为第一关键字,以y为第二关键词从小到大排序
    }
}a[N];
int f[N][K];

int main() {
    // 输入 n 和 k
    scanf("%d%d", &n, &k);
    
    // 输入 n 个整数点的坐标
    for(int i = 1; i <= n; i++)
        scanf("%d%d", &a[i].x, &a[i].y);
    
    // 对点进行排序
    sort(a + 1, a + 1 + n);
    
    // 动态规划过程
    for(int i = 1; i <= n; i++) {
        f[i][k] = 1; // 初始化第 i 个点,剩余 k 个自由点的情况
        for(int j = 0; j <= k; j++) {
            for(int t = 1; t < i; t++) {
                // 序列必须符合题意,横坐标和纵坐标均单调不减
                if(a[t].x > a[i].x || a[t].y > a[i].y) continue;
                
                // 计算从点 t 到点 i 之间需要加的自由点数
                int dx = abs(a[i].x - a[t].x);
                int dy = abs(a[i].y - a[t].y);
                int d = dx + dy - 1;
                
                // 如果需要加的自由点超过 k 个,跳过
                if(j + d > k) continue;
                
                // 状态转移
                f[i][j] = max(f[i][j], f[t][j + d] + d + 1);
            }
        }
    }
    
    // 最终答案
    int ans = 0;
    for(int i = 1; i <= n; i++)
        for(int j = 0; j <= k; j++)
            ans = max(ans, j + f[i][j]);  // 考虑剩余自由点的情况
    
    // 输出答案
    cout << ans;
    
    return 0;
}

# 代码解析

  1. 输入处理:首先输入 nnkk,接着读取每个点的坐标。
  2. 排序:对所有点按 xxyy 进行排序,确保动态规划过程中的序列是合法的。
  3. 动态规划:使用三层嵌套循环,逐步转移状态。最内层循环计算从一个点到另一个点需要添加的自由点数,并更新状态。
  4. 输出答案:通过遍历所有可能的状态,找到能够构成的最大序列长度并输出。

# 小结

这个问题本质上是一个二维的最长子序列问题。通过引入动态规划来解决状态转移,结合适当的自由点添加条件,最终可以在 O(n2k)O(n^2 k) 的时间复杂度内解决问题。

上次更新: 2024-10-19 03:40:51