2024年CSP-J暑假冲刺训练营(1):枚举

一、时空复杂度

时间复杂度与空间复杂度,即根据题目可能输入的最大情况评测算法用时、空间,不能超过 1 0 7 10^7 107,否则时间复杂度会报 TLE,空间复杂度会报 MLE

二、枚举算法

枚举的几个要素:

  • 枚举元素
  • 枚举范围
  • 枚举(输出)元素是否从小到大
  • 是否可以通过计算避免重复枚举

三、gcd 模板

while (a % b != 0) {
    r = a % b;
    a = b
    b = r;
}
cout << "gcd: " << b << endl;

四、例题

1. 最大公约数与最小公倍数问题

题目描述

输入两个正整数 x 0 , y 0 x_0,y_0 x0,y0 2 ≤ x 0 < 100000 2\le x_0<100000 2x0<100000 2 ≤ y 0 ≤ 100000 2\le y_0\le100000 2y0100000),求出满足下列条件的 P , Q P,Q P,Q 的个数条件:

  1. P , Q P,Q P,Q 是正整数
  2. 要求 P , Q P,Q P,Q x 0 x_0 x0 为最大公约数,以 y 0 y_0 y0 为最小公倍数。

试求:满足条件的所有可能的两个正整数的个数。

输入描述

输入二个正整数 x 0 , y 0 x_0,y_0 x0,y0

输出描述

输出满足条件的 P , Q P,Q P,Q 的个数。

样例1

输入

3 60

输出

4

根据最大公约数最小公倍数定理:
gcd ( a , b ) × lcm ( a , b ) = a b x 0 × y 0 = P Q \text{gcd}(a,b)\times\text{lcm}(a,b)=ab \\ x_0\times y_0=PQ gcd(a,b)×lcm(a,b)=abx0×y0=PQ

p p p 的枚举范围: x 0 ∼ y 0 x_0\sim y_0 x0y0
Q = x 0 y 0 p Q=\frac{x_0 y_0}{p} Q=px0y0

然后我们可以根据复核的方法重新求解,然后再将 cnt \text{cnt} cnt 增加。

参考答案如下:

#include <iostream>
using namespace std;

long long x, y, cnt;

int main() {
    cin >> x >> y;
    for (long long p = x; p <= y; p++) {
        long long q = x * y / p;
        if (x * y % p == 0) {
            long long a = p, b = q, r;
            while (a % b != 0) {
                r = a % b;
                a = b;
                b = r;
            }
            if (b == x) cnt++;
        } else {
            continue;
        }
    }
    cout << cnt;
    return 0;
}

2. [NOIP2008 提高组] 火柴棒等式

题目描述

给你 n n n 根火柴棍,你可以拼出多少个形如 A + B = C A+B=C A+B=C 的等式?等式中的 A , B , C A,B,C A,B,C 是用火柴棍评出来的整数(若该数非零,则最高位不能为 0 0 0)。
注意:
1、加号与等号各自取两根火柴棍
2、如果 A ≠ B A≠B A=B,则 A + B = C A+B=C A+B=C B + A = C B+A=C B+A=C 视为不同的等式( A , B , C ≥ 0 A,B,C≥0 A,B,C0
3、 n n n 根火柴棍必须全部用上

输入描述

一个整数 n n n 1 ≤ n ≤ 24 1≤n≤24 1n24

输出描述

一个整数,能拼成的不同等式的数目。

样例1

输入

14

输出

2

参考代码如下:

#include <iostream>
using namespace std;

int n, cnt;
int a[10] = {6, 2, 5, 5, 4, 5, 6, 3, 7, 6};

int f(int n) {
    if (n == 0) return a[0];
    int x = n, sum = 0;
    while (x) {
        sum += a[x%10];
        x /= 10;
    }
    return sum;
}

int main() {
    cin >> n;
    n -= 4;
    for (int a = 0; a <= 2000; a++) {
        for (int b = 0; b <= 2000; b++) {
            int k = f(a) + f(b) + f(a+b);
            if (k == n) {
                cnt++;
            }
        }
    }
    cout << cnt;
    return 0;
}

3. 连续自然数之和

题目描述

对一个给定的自然数 n n n,求出所有的连续的自然数段,这些连续的自然数段中的全部数之和为 n n n。例子: 1998 + 1999 + 2000 + 2001 + 2002 = 10000 1998+1999+2000+2001+2002 = 10000 1998+1999+2000+2001+2002=10000,所以从 1998 1998 1998 2002 2002 2002 的一个自然数段为 n = 10000 n=10000 n=10000 的一个解。

输入描述

包含一个整数的单独一行给出n的值。

输出描述

每行两个自然数,给出一个满足条件的连续自然数段中的第一个数和最后一个数,两数之间用一个空格隔开,所有输出行的第一个按从小到大的升序排列,对于给定的输入数据,保证至少有一个解。

样例1

输入

10000

输出

18 142
297 328
388 412
1998 2002

提示

10 ≤ n ≤ 2000000 10≤n≤2000000 10n2000000

#include <iostream>
using namespace std;

int n;

int main() {
    cin >> n;
    for (int i = 1; i <= n/2; i++) {
        int sum = 0;
        for (int j = i; ; j++) {
            sum += j;
            if (sum >= n) {
                if (sum == n) {
                    cout << i << " " << j << endl;
                }
                break;
            }
        }
    }
    return 0;
}

4. 无穷的序列

题目描述

有一个无穷序列如下:
110100100010000100000 ⋯ 110100100010000100000\cdots 110100100010000100000
请你找出这个无穷序列中指定位置上的数字。

输入描述

第一行一个正整数 N N N,表示询问次数;
接下来的 N N N 行每行一个正整数 A i A_i Ai 表示在序列中的位置。

输出描述

输出为 N N N 行,每行为 0 / 1 0/1 0/1,表示序列第 A i A_i Ai 位上的数字。

样例1

输入

4
3
14
7
6 

输出

0
0
1
0
#include <iostream>
#include <cmath>
using namespace std;

int n, x;

int main() {
    cin >> n;
    while (n--) {
        cin >> x;
        x--;
        int y = sqrt(2*x);
        if (y*(y+1) == x*2) {
            cout << "1\n";
        } else {
            cout << "0\n";
        }
    }
    return 0;
}

如果发生了超时的问题,可以用快读实现:

ios::sync_with_stdio(0);
cin.tie(0);

也可以用快读函数:

int read() {
    int x = 0;
    char c = getchar();
    while (c < '0' || c > '9') {
        c = getchar();
    }
    while (c >= '0' && c <= '9') {
        x = (x*10) + (c-'0');
        c = getchar();
    }
    return x;
}

5. [NOIP 2000 普及组] 税收与补贴问题

题目描述

每样商品的价格越低,其销量就会相应增大。现已知某种商品的成本及其在若干价位上的销量(产品不会低于成本销售),并假设相邻价位间销量的变化是线性的且在价格高于给定的最高价位后,销量以某固定数值递减。(我们假设价格及销售量都是整数)
对于某些特殊商品,不可能完全由市场去调节其价格。这时候就需要政府以税收或补贴的方式来控制。(所谓税收或补贴就是对于每个产品收取或给予生产厂家固定金额的货币)
你是某家咨询公司的项目经理,现在你已经知道政府对某种商品的预期价格,以及在各种价位上的销售情况。要求你确定政府对此商品是应收税还是补贴的最少金额(也为整数),才能使商家在这样一种政府预期的价格上,获取相对其他价位上的最大总利润。
总利润 = 单位商品利润 × 销量 单位商品利润 = 单位商品价格–单位商品成本(–税金 或者  + 补贴) 总利润=单位商品利润\times销量\\单位商品利润=单位商品价格– 单位商品成本(– 税金\ 或者\ +补贴) 总利润=单位商品利润×销量单位商品利润=单位商品价格单位商品成本(税金 或者 +补贴)

输入描述

输入的第一行为政府对某种商品的预期价,第二行有两个整数,第一个整数为商品成本,第二个整数为以成本价销售时的销量售,以下若干行每行都有两个整数,第一个为某价位时的单价,第二个为此时的销量,以一行 -1 表示所有已知价位及对应的销量输入完毕,输入的最后一行为一个单独的整数表示在已知的最高单价外每升高一块钱将减少的销量。

输出描述

输出有两种情况:若在政府预期价上能得到最大总利润,则输出一个单独的整数,数的正负表示是补贴还是收税,数的大小表示补贴或收税的金额最小值。若有多解,取绝对值最小的输出。
如在政府预期价上不能得到最大总利润,则输出 "NO SOLUTION"

样例1

输入

31
28 130
30 120
31 110
-1 –1
15

输出

4

提示

所有输入的数字不超过 100000 100000 100000

CCF \text{CCF} CCF 官方参考答案:

#include<iostream>
using namespace std;
int num[100010];
double k[100010];
int main(){
    int ex,cb,c,n,lc,ln;
    cin>>ex;
    cin>>c>>n;
    cb=c;
    while(c!=-1&&n!=-1){
        lc=c,ln=n;
        num[c]=n;
        cin>>c>>n;
        k[lc]=1.0*(n-ln)/(c-lc);
    }
    int cnt;
    cin>>cnt;
    double kk;
    int nn,cc;
    for(int i=cb;i<=lc;i++){
        if(num[i]!=0){
            cc=i;nn=num[i];kk=k[i];
        }else{
            num[i]=(i-cc)*kk+nn;
        }
    }
    while(ln-cnt>0){
        lc++;ln-=cnt;num[lc]=ln;
    }
    int id,maxx=0;
    for(int i=cb;i<=lc;i++){
        if((i-cb)*num[i]>maxx){
            id=i;
            maxx=(i-cb)*num[i];
        }
    }
    if(id==ex)cout<<0;
    else if(id>ex){
        for(int x=1;;x++){
            maxx=0;id=0;
            for(int i=cb;i<=lc;i++){
                if((i-cb+x)*num[i]>=maxx){
                    id=i;maxx=(i-cb+x)*num[i];
                }
            }
            if(id==ex){
                cout<<x; return 0;
            }
        }
    }else{
        for(int x=-1;;x--){
            maxx=0;id=0;
            for(int i=cb;i<=lc;i++){
                if((i-cb+x)*num[i]>=maxx){
                    id=i;maxx=(i-cb+x)*num[i];
                }
            }
            if(id==ex){
                cout<<x; return 0;
            }
        }
    }
    
}

6. [蓝桥杯 2018 省 A] 倍数问题

题目描述

众所周知,小葱同学擅长计算,尤其擅长计算一个数是否是另外一个数的倍数。但小葱只擅长两个数的情况,当有很多个数之后就会比较苦恼。现在小葱给了你 n n n 个数,希望你从这 n n n 个数中找到三个数,使得这三个数的和是 K K K 的倍数,且这个和最大。数据保证一定有解。

输入描述

从标准输入读入数据。
第一行包括 2 2 2 个正整数表示 n , K n,K n,K
第二行 n n n 个正整数,代表给定的 n n n 个数。

输出描述

输出一行一个整数代表所求的和。

样例1

输入

4 3
1 2 3 4

输出

9

提示

【样例解释】
选择 2 , 3 , 4 2,3,4 2,3,4

#include <iostream>
using namespace std;

int maxn;
int n, k, x, y;
int a[1025][5];

int main() {
    cin >> n >> k;
    for (int i = 1; i <= n; i++) {
        cin >> x;
        y = x % k;
        if (x > a[y][0]) {
            a[y][2] = a[y][1];
            a[y][1] = a[y][0];
            a[y][0] = x;
        } else if (x > a[y][1]) {
            a[y][2] = a[y][1];
            a[y][1] = x;
        } else if (x > a[y][2]) {
            a[y][2] = x;
        }
    }
    
    for (int s = 0; s <= 2 * k; s += k) {
        for (int i = 0; i < k; i++) {
            for (int j = 0; j < k; j++) {
                int t = s - i - j;
                if (t >= k || t < 0) continue;
                int tmp = a[i][0];
                tmp += a[j][i==j];
                tmp += a[t][(i==t)+(j==t)];
                maxn = max(maxn, tmp);
            }
        }
    }
    cout << maxn;
    return 0;
}

7. [USACO1.3] 最长的回文 Calf Flac

题目描述

\如果你给无限只母牛和无限台巨型便携式电脑(有非常大的键盘 ), 那么母牛们会制造出世上最棒的回文。你的工作就是去寻找这些牛制造的奇观(最棒的回文)。
在寻找回文时不用理睬那些标点符号、空格(但应该保留下来以便做为答案输出), 只用考虑字母 A ∼ Z A\sim Z AZ a ∼ z a\sim z az。要你寻找的最长的回文的文章是一个不超过 20 , 000 20,000 20,000 个字符的字符串。我们将保证最长的回文不会超过 2 , 000 2,000 2,000 个字符(在除去标点符号、空格之前)。

输入描述
输入文件不会超过 20 , 000 20,000 20,000 字符。这个文件可能一行或多行,但是每行都不超过 80 80 80 个字符(不包括最后的换行符)。

输出描述

输出的第一行应该包括找到的最长的回文的长度。
下一行或几行应该包括这个回文的原文(没有除去标点符号、空格),把这个回文输出到一行或多行(如果回文中包括换行符)。
如果有多个回文长度都等于最大值,输出最前面出现的那一个。

样例1

输入

Confucius say: Madam, I'm Adam. 

输出

11
Madam, I'm Adam
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;

int pos[20005];
int i, j;
string s, x, d;
int maxn, maxs, maxe;

bool isPallin(string s) {
    string rs = s;
    reverse(rs.begin(), rs.end());
    if (s == rs) {
        return 1;
    } else {
        return 0;
    }
}

int main() {
    while (getline(cin, x)) {
        s += x;
        s += "\n";
    }
    
    for (i = 0; i < s.length(); i++) {
        if (s[i] >= 'A' && s[i] <= 'Z') {
            d += (char)s[i]+32;
            pos[j] = i;
            j++;
        } else if (s[i] >= 'a' && s[i] <= 'z') {
            d += s[i];
            pos[j] = i;
            j++;
        }
    }
    
    for (int i = 0; i < d.length(); i++) {
        for (int j = i+maxn; j < i+2010; j++) {
            if (j >= d.length()) break;
            bool flag = true;
            for (int k = i; k <= j; k++) {
                if (d[k] != d[i-k+j]) {
                    flag = false;
                    break;
                }
            }
            if (flag) {
                if (j-i+1 > maxn) {
                    maxn = j-i+1;
                    maxs = i, maxe = j;
                }
            }
        }
    }
    
    cout << maxn << endl;
    for (int i = pos[maxs]; i <= pos[maxe]; i++) {
        cout << s[i];
    }
    return 0;
}

五、易错

  1. 不能让其他数据类型和 string 一起输入,否则会继续读入第一行的剩余内容。下面是一个错误示例:
    int n;
    cin >> n;
    for (int i = 1; i <= n; i++) {
        string s;
        getline(cin, s);
        cout << s << endl;
    }
    
  2. while 循环的条件如果是 0 0 0 就永远都不会进循环体,因此对于 0 0 0 的情况最好特判一下,否则会有样例无法通过。

附录

1. 数位分离模板

while (x) {
    sum += x % 10;
    x /= 10;
}

2. gcd 模板

while (a % b != 0) {
    r = a % b;
    a = b
    b = r;
}

3. 快读函数模板

int read() {
    int x = 0;
    char c = getchar();
    while (c < '0' || c > '9') {
        c = getchar();
    }
    while (c >= '0' && c <= '9') {
        x = (x*10) + (c-'0');
        c = getchar();
    }
    return x;
}
  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值