P1563 [NOIP 2016 提高组] 玩具谜题

记录40

#include<bits/stdc++.h>
using namespace std;
struct node{
	int num;
	string name;
};
int main(){
	int n,m,d,p;
	cin>>n>>m;
	node a[100010]={};
	for(int i=0;i<n;i++) cin>>a[i].num>>a[i].name;
	int t=0;
	string str;
	while(m--){
		cin>>d>>p;
		if(a[t].num==d){//注意逆时针,判断朝向跟左右手
			t=(t-p+n)%n;
			str=a[t].name;
		}
		else{
			t=(t+p)%n;
			str=a[t].name;
		}
	}
	cout<<str;
	return 0;
} 

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


突破点

这个谜题中玩具小人的朝向非常关键,因为朝内和朝外的玩具小人的左右方向是相反的:面朝圈内的玩具小人,它的左边是顺时针方向,右边是逆时针方向;而面向圈外的玩具小人,它的左边是逆时针方向,右边是顺时针方向。

以逆时针为顺序给出每个玩具小人的朝向和职业。其中 0 表示朝向圈内,1 表示朝向圈外。保证不会出现其他的数。字符串长度不超过 10 且仅由英文字母构成,字符串不为空,并且字符串两两不同。

两个整数 ai​,si​,表示第 i 条指令。若 ai​=0,表示向左数 si​ 个人;若 ai​=1,表示向右数 si​ 个人


思路

  1. 每个小人带两个数据👉结构体
  2. 对圈的放行进行判断👉面朝圈的方向+左右手
  3. 围着圈进行移动👉取余的处理手法

代码简析

#include<bits/stdc++.h>
using namespace std;
struct node{
	int num;
	string name;
};
int main(){
	int n,m,d,p;
	cin>>n>>m;
	node a[100010]={};
	for(int i=0;i<n;i++) cin>>a[i].num>>a[i].name;
	int t=0;
	string str;
	...
	return 0;
} 

将小人的情况输入进去

for循环从编号0开始,方便后面进行圈内循环(数组实现循环)

int t=0;           t代表目前的小人数
string str;       str存小人的名字

#include<bits/stdc++.h>
using namespace std;
struct node{
	int num;
	string name;
};
int main(){
	int n,m,d,p;
	cin>>n>>m;
	node a[100010]={};
	for(int i=0;i<n;i++) cin>>a[i].num>>a[i].name;
	int t=0;
	string str;
	while(m--){
		cin>>d>>p;
		if(a[t].num==d){//注意逆时针,判断朝向跟左右手
			t=(t-p+n)%n;
			str=a[t].name;
		}
		else{
			t=(t+p)%n;
			str=a[t].name;
		}
	}
	cout<<str;
	return 0;
} 

d是手的左右方向

p是移动几个人

因为是逆时针,圈外网左,圈内往右,其实是一个方向

取余其实就是一种循环操作

五个人编号:0 1 2 3 4 组成一个圈

以编号二为例

逆时针转动三个人   (2-3+5)%5

顺时针转动三个人   (2+3+5)%5    


补充

取余运算的"圈内循环"特性详解


1. 核心机制:有限状态自动回归

取余运算的本质是将无限整数域映射到有限集合 [0, mod-1],形成闭环循环。

for (int i = 0; i < 20; i++) {
    cout << i % 5 << " ";  // 输出:0 1 2 3 4 0 1 2 3 4 0 1 2 3 4...
}

特点:无论 i 增长到多大,结果永远在 [0,4] 这5个状态内循环,如同钟表的指针。


2. 数学原理:模运算的周期性

对任意整数 a 和正整数 mod,满足:

a % mod ∈ [0, mod-1]
(a + mod) % mod = a % mod  // 周期为mod

几何意义:数轴被折叠成 mod 个点的圆圈,每前进 mod 步回到原点。


3. 竞赛七大应用场景

场景1:数组环形访问(最高频)

int a[5] = {10,20,30,40,50};
int idx = 7;  // 越界索引

// 循环访问:自动回到数组开头
cout << a[idx % 5];  // 输出40(等价于a[2])

// 通用模板:循环队列
int front = 0, rear = 0, q[100];
rear = (rear + 1) % 100;  // 队尾循环移动
场景2:哈希散列(冲突处理)

// 线性探测法:冲突时循环向后找空位
int hash_table[MOD];
int pos = key % MOD;
while (hash_table[pos] != 0) {
    pos = (pos + 1) % MOD;  // 循环探测,不越界
}
场景3:随机数生成(循环采样)

// 在游戏或模拟中循环遍历玩家
int current_player = 0;
while (game_running) {
    current_player = (current_player + 1) % n;  // 循环轮到下一个玩家
    // 处理current_player的行动
}
场景4:状态压缩与循环移位

int state = 7;  // 二进制111
int rotated = (state << 1) | (state >> 2);  // 循环左移
// 取余保证位数不变:rotated &= (1<<3)-1 = 7
场景5:周期性事件调度

// 7天为周期的任务
int day_of_week = (start_day + offset) % 7;  // 0=周日,...,6=周六

// 月份循环(配合数组)
int days_in_month[] = {31,28,31,30,31,30,31,31,30,31,30,31};
int next_month = (current_month + 1) % 12;  // 12月后回到1月
场景6:进制转换与循环进位

// 大数取模:快速计算10^n mod m
long long power_mod(long long n, long long m) {
    long long ans = 1;
    for (int i = 0; i < n; i++) {
        ans = (ans * 10) % m;  // 每次循环都在[0,m-1]内
    }
    return ans;
}
场景7:循环计数器与索引

// 统计字符出现次数(循环映射到数组)
char c;
int cnt[26] = {0};
cin >> c;
if (c >= 'a' && c <= 'z') {
    cnt[c - 'a']++;  // 隐式取余:字母循环映射到0-25
}

// 显式取余版本:处理循环输入
for (int i = 0; i < n; i++) {
    int bucket = i % 26;  // 循环使用26个桶
    // 处理bucket
}

4. 负数取余的陷阱与修复

C++负数取余行为

-7 % 3 = -1  // 余数符号与被除数一致
7 % -3 = 1   // 余数符号与被除数一致

问题:负余数不在 [0, mod-1] 的"圈内",破坏循环性。

标准化修复

// 通用模板:将余数转换到[0, mod-1]
int mod = 5;
int x = -7;
int normalized = (x % mod + mod) % mod;  // 结果:3

// 竞赛宏定义
#define MOD(a, m) ((a) % (m) + (m)) % (m)

5. 竞赛高频技巧与模板

技巧1:快速判断循环重合点

// 两指针以不同速度循环移动,必在mod步内相遇
int slow = 0, fast = 0;
do {
    slow = (slow + 1) % mod;
    fast = (fast + 2) % mod;
} while (slow != fast);  //  Floyd判圈算法核心
技巧2:循环差值计算

// 计算环形数组中两点距离(顺时针)
int dist = (b - a + n) % n;  // 无论b<a还是b>a都正确
技巧3:倍增循环

// 快速幂的取余循环
long long pow_mod(long long a, long long b, long long mod) {
    long long ans = 1;
    while (b) {
        if (b & 1) ans = (ans * a) % mod;
        a = (a * a) % mod;
        b >>= 1;
    }
    return ans;
}

6. 常见错误与调试

错误代码问题正确写法
a[-1] % 3数组越界a[(i+mod-1)%mod]
i % 0除零错误确保 mod > 0
cnt[hash % 1000]负哈希值cnt[(hash % 1000 + 1000) % 1000]
for(i=0; i<=n; i++) a[i%k]多循环一次i < n 不是 i <= n

7. 核心要点总结

  1. 循环本质a % mod 将整数映射到有限集合 [0, mod-1]

  2. 周期公式(a + k*mod) % mod = a % mod(k为任意整数)

  3. 负数修复(x % mod + mod) % mod 保证结果非负

  4. 数组循环arr[i % n] 是实现环形缓冲区的核心

  5. 性能:取余运算 O(1),但比加减法慢,在极大量运算时可考虑位优化(mod=2^k 时用 & (mod-1)

  6. 边界:确保 mod > 0,否则触发运行时错误

竞赛箴言:遇到"循环"、"轮转"、"周期"、"哈希冲突"等关键词,立刻想到取余运算。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值