P1008 [NOIP 1998 普及组] 三连击

记录37

#include<bits/stdc++.h>
using namespace std;
int main(){
	int a[11]={},cnt,i;
	for(i=192;i<333;i++){
		 memset(a,0,sizeof(a));
  	cnt=0;  a[i/100]=a[i/10%10]=a[i%10]=a[2*i/100]=a[2*i/10%10]=a[2*i%10]=a[3*i/100]=a[3*i/10%10]=a[3*i%10]=1;
		for(int j=1;j<=9;j++) 	cnt+=a[j];
		if(cnt==9) cout<<i<<" "<<i*2<<" "<<i*3<<endl;	
	}
	return 0;
} 

题目传送门https://www.luogu.com.cn/problem/P1008


突破点

将 1,2,…,9 共 9 个数分成 3 组,分别组成 3 个三位数,且使这 3 个三位数构成 1:2:3 的比例,试求出所有满足条件的 3 个三位数。


思路

  1. 用桶来记录数字的出现情况
  2. 取出记录的每个数字
  3. 符合条件就输出

代码简析

#include<bits/stdc++.h>
using namespace std;
int main(){
	int a[11]={},cnt,i;
	for(i=192;i<333;i++){
		 memset(a,0,sizeof(a));
  	    cnt=0;      
        ...
        ...
	}
	return 0;
} 

a[11]数组用来记录1~9出现的数字情况

for(i=192;i<333;i++){}   因为三个数最大也不会超过999,所以i取到333

memset(a,0,sizeof(a));     数组初始化,在补充部分详细介绍

cnt=0;  每次都要重新记录数字出现的个数

#include<bits/stdc++.h>
using namespace std;
int main(){
	int a[11]={},cnt,i;
	for(i=192;i<333;i++){
		 memset(a,0,sizeof(a));
  	cnt=0;  a[i/100]=a[i/10%10]=a[i%10]=a[2*i/100]=a[2*i/10%10]=a[2*i%10]=a[3*i/100]=a[3*i/10%10]=a[3*i%10]=1;
		for(int j=1;j<=9;j++) 	cnt+=a[j];
		if(cnt==9) cout<<i<<" "<<i*2<<" "<<i*3<<endl;	
	}
	return 0;
} 

a[i/100]=a[i/10%10]=a[i%10]=a[2*i/100]=a[2*i/10%10]=a[2*i%10]=a[3*i/100]=a[3*i/10%10]=a[3*i%10]=1;

对每个数进行取出来,因为是1:2:3的关系,所以直接乘对应的倍数

for(int j=1;j<=9;j++)     cnt+=a[j];        统计出现的桶数字出现的个数

if(cnt==9){}         符合条件就输出

补充

memset 函数详解与使用指南


1. 函数原型与头文件

#include <cstring>  // C++ 推荐
// 或 #include <string.h>  // C风格

void *memset(void *s, int c, size_t n);

参数说明

  • s:指向要填充的内存块的起始地址

  • c:要填充的值(0~255,但实际以 int 类型传递)

  • n:要填充的字节数


2. 最常用场景:清零操作

int a[100];
memset(a, 0, sizeof(a));  // 将整个数组每个元素置0

// 等价于
for (int i = 0; i < 100; i++) a[i] = 0;

优势:代码简洁,执行速度极快(通常由硬件指令优化)。


3. 典型应用场景

场景代码示例说明
静态数组清零int arr[1000]; memset(arr, 0, sizeof(arr));将数组所有元素设为0
动态数组清零int* p = new int[100]; memset(p, 0, 100 * sizeof(int));注意:sizeof(p)仅指针大小
结构体清零Student s; memset(&s, 0, sizeof(s));快速初始化所有成员为0/NULL
二维数组清零int g[100][100]; memset(g, 0, sizeof(g));sizeof(g)自动计算总字节数

4. 按字节填充原理与陷阱

memset 按字节填充,这是其强大与危险的根源:

  • 清零(0):任何类型都适用,因为全0字节对数值类型就是0,对指针就是NULL

  • 置-1memset(arr, -1, sizeof(arr)); 也普遍适用,因为-1的补码是0xFF

  • 陷阱不能填充非0xFF的值到多字节类型

int arr[10];
memset(arr, 1, sizeof(arr));  // 危险!
// 每个int元素实际是 0x01010101 = 16843009,不是1!

5. 不同类型数据的处理

数值类型

// ✅ 正确:清零或置-1
memset(arr, 0, sizeof(arr));
memset(arr, -1, sizeof(arr));

// ❌ 错误:意图赋其他值
memset(arr, 1, sizeof(arr));  // 实际得到16843009

字符数组

char str[100];
memset(str, 'A', sizeof(str));  // ✅ 正确:所有字符为'A'
// 或填充空字符串
memset(str, 0, sizeof(str));    // 全'\0',等价于空字符串

布尔数组

bool flag[100];
memset(flag, 0, sizeof(flag));  // ✅ 全false
memset(flag, 1, sizeof(flag));  // ✅ 全true(所有字节为1)

6. 动态内存的特殊处理

// ❌ 错误示范
int* p = new int[100];
memset(p, 0, sizeof(p));  // 只清零了指针本身(4/8字节)

// ✅ 正确写法
memset(p, 0, 100 * sizeof(int));  // 计算总字节数
// 或
int n = 100;
int* p = new int[n];
memset(p, 0, n * sizeof(int));

7. 与初始化/赋值的区别

方式时机效率适用场景
int a[100] = {0};编译时最快静态数组,只能初始化为0
memset(a, 0, sizeof(a));运行时极快任意时刻,可重复清零
fill(a, a+100, 0);运行时较快可赋任意值,需头文件<algorithm>
for循环赋值运行时较慢灵活,但代码冗余

8. 竞赛高频错误

struct Node {
    int val;
    vector<int> adj;
};

Node nodes[100];
memset(nodes, 0, sizeof(nodes));  // ❌ 危险!
// 仅将val清零,vector内部状态被破坏

致命问题memset 对包含非POD类型(如string, vector)的结构体是未定义行为,会导致程序崩溃。

正确做法

for (int i = 0; i < 100; i++) {
    nodes[i].val = 0;
    nodes[i].adj.clear();
}

9. 性能与适用性
  • 性能:在-O2优化下,memset 通常比手动循环快数倍

  • 适用性:仅适用于POD类型(Plain Old Data),如基本类型、数组、不含复杂成员的struct

  • 不推荐:含string, vector, map等STL成员的结构体


10. 竞赛最佳实践

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

const int MAXN = 1e5 + 5;
int arr[MAXN];
bool vis[MAXN];
long long dp[MAXN][2];

int main() {
    // ✅ 标准清零模板
    memset(arr, 0, sizeof(arr));
    memset(vis, 0, sizeof(vis));
    memset(dp, 0, sizeof(dp));  // 对long long同样有效
    
    // ✅ 多组数据测试时的重置
    int T;
    cin >> T;
    while (T--) {
        int n;
        cin >> n;
        memset(arr, 0, sizeof(int) * (n + 1));  // 只清零前n+1个
        // ... 处理逻辑
    }
    
    // ✅ 结构体初始化(仅含POD成员)
    struct Edge { int u, v, w; };
    Edge edges[MAXN];
    memset(edges, 0, sizeof(edges));
    
    return 0;
}

核心要点总结

  1. 万能公式memset(arr, 0, sizeof(arr)); 永远安全

  2. 仅限-1memset(arr, -1, sizeof(arr)); 对整数数组有效

  3. 动态数组:必须手动计算字节数 n * sizeof(type)

  4. 结构体:仅当所有成员都是POD类型时才可用

  5. 速度:竞赛中清零操作应优先使用memset,比循环快且代码简洁

  6. 陷阱绝不用memset赋1或其他非0xFF值,结果不符合预期

掌握以上用法,可确保在CSP竞赛中安全、高效地使用memset

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值