P1190 [NOIP 2010 普及组] 接水问题

记录49

#include<bits/stdc++.h>
using namespace std;
int main(){
	int n,m,water[110]={},t,min_=999,num=0;
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		cin>>t;
		for(int j=1;j<=m;j++){
			if(min_>water[j]){
				min_=water[j];
				num=j;
        //printf("最小情况:%d   编号:%d\n",min_,num);
			}
		}
		water[num]+=t;
    min_+=water[num];
	}
	sort(water+1,water+m+1);
  cout<<water[m];
	return 0;
} 

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


突破点

每个龙头每秒钟的供水量相等,均为 1

👉时间跟接水量一样了

这些同学按接水顺序从 1 到 n 编号,i 号同学的接水量为 wi​。接水开始时,1 到 m 号同学各占一个水龙头,并同时打开水龙头接水。当其中某名同学 j 完成其接水量要求 wj​ 后,下一名排队等候接水的同学 k 马上接替 j 同学的位置开始接水。

👉先接完水的先离开

按照上述接水规则,问所有同学都接完水需要多少秒。

👉最后一个接完水的时间


思路

  1. 输入要接水的量(即时间)
  2. 遍历最快完成的水龙头让同学过去排队
  3. 更新排队后水龙头的数据

代码简析

#include<bits/stdc++.h>
using namespace std;
int main(){
	int n,m,water[110]={},t,min_=999,num=0;
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		cin>>t;
		for(int j=1;j<=m;j++){
			if(min_>water[j]){
				min_=water[j];
				num=j;
        //printf("最小情况:%d   编号:%d\n",min_,num);
			}
		}
		water[num]+=t;
    min_+=water[num];
	}
	sort(water+1,water+m+1);
  cout<<water[m];
	return 0;
} 

双重for循环就可以解决此题

外层for循环一个个接水的人,内层for循环找到最快结束的水龙头编号

if(min_>water[j]){}👉寻找最快结束的水龙头

min_=water[j];👉更新最快水龙头的时间值

num=j;👉记录下来最快编号

water[num]+=t;👉最快的水龙头排队

min_+=water[num];👉当前最快水龙头的时间更新

sort(water+1,water+m+1);👉水龙头结束时间由快到慢

cout<<water[m];👉最后一个结束的时间


补充

CSP-J 模拟类题目技巧完全汇总


一、什么是"模拟题"(CSP-J核心题型)

定义:严格按题目描述的规则、流程、步骤,一步步编码实现,不依赖复杂算法,考察细节处理编码能力

CSP-J中的占比第1-2题80%是模拟,第3题50%是模拟。


二、高频模拟题型与识别特征

题型1:流程模拟(考频★★★★★)

特征:题目描述一个操作流程(如计算器、投票系统、排队) 关键词:"按照规则"、"依次处理"、"每一步"

例子

  • 2023 T1 小苹果:按规则分苹果

  • 2022 T1 乘方:按步骤计算幂

题型2:字符串模拟(考频★★★★☆)

特征:按规则修改、匹配、构造字符串 关键词:"删除字符"、"替换"、"匹配模式"

例子

  • 密码生成器

  • 字符串加密解密

题型3:日期/时间模拟(考频★★★☆☆)

特征:计算日期差、星期几、时间跨度 关键词:"第几天"、"经过多少天"、"星期"

题型4:游戏模拟(考频★★☆☆☆)

特征:模拟棋类游戏、走格子、胜负判定 关键词:"移动"、"轮到谁"、"获胜条件"


三、通用解题步骤(模板)

Step 1:读题画流程图(纸笔必备)

输入 → 初始化状态 → 循环处理 → 更新状态 → 输出结果
            ↑_______________↓

Step 2:定义状态变量

// 根据题目定义所有需要的状态
int day = 1;          // 当前天数
int player = 0;       // 当前玩家(0或1)
string state = "init";// 当前状态
vector<int> queue;    // 队列状态

Step 3:主循环框架

while (!终止条件) {
    // 1. 读取当前操作/输入
    cin >> op;
    
    // 2. 按规则处理
    if (op == "A") {
        // 处理A规则
        更新状态();
    } else if (op == "B") {
        // 处理B规则
        更新状态();
    }
    
    // 3. 边界检查(越界、非法、结束)
    if (非法状态) break;
    
    // 4. 记录结果
    ans.push_back(当前结果);
}

Step 4:输出结果

// 按题目要求格式输出
for (int x : ans) cout << x << endl;

四、核心技巧与防错指南

技巧1:下标从0还是从1?统一!

// ✅ 推荐:从1开始,避免负下标
int a[N];
for (int i = 1; i <= n; i++) cin >> a[i];

// ❌ 错误:混用0和1,导致计算错误
a[i-1] = a[i] + 1;  // 容易越界

技巧2:状态变量初始化

// 所有状态必须初始化,否则随机值
int step = 0;           // 步数
bool gameOver = false;  // 游戏结束标记
string curPlayer = "A"; // 当前玩家

技巧3:字符串模拟用数组比string快

// 频繁修改字符时:
char s[1005];  // ✅ 用char[],直接s[i]='x'
// 比 string s; s[i]='x'; 更快

技巧4:大数组用全局,模拟用局部

// 数据存储(全局,大)
int a[1010][1010];

// 模拟状态(局部,小)
int x = 0, y = 0;
while (true) { ... }

技巧5:调试技巧:中间输出法

// 在关键步骤输出状态,快速定位错误
if (debug) {
    cout << "Step=" << step << " Player=" << curPlayer 
         << " Pos=" << x << "," << y << endl;
}

五、常见陷阱与避坑指南

陷阱1:边界条件漏掉

  • 错误:循环到i < n,但题目可能有i <= n

  • 应对:读题时把所有≤、≥、<、>圈出来

陷阱2:状态更新顺序错误

// 错误示范:先更新位置,再检查是否越界
x += dx;  // 可能越界
if (x > n) x = 1;  // 补救,但逻辑混乱

// 正确示范:先检查,再更新
int nx = x + dx;
if (nx > n) nx = 1;
x = nx;

陷阱3:字符串结束符'\0'

char s[1005];
cin >> s;
int len = strlen(s);  // ✅ 自动找到\0
s[len] = 'a';  // ❌ 可能越界!
s[len+1] = '\0';  // ✅ 必须保证结尾有\0

陷阱4:多组数据未清空状态

int T; cin >> T;
while (T--) {
    // ❌ 错误:未清空上次状态
    // vector<int> q; 应该在这里定义
    
    // ✅ 正确:每组数据前清空
    memset(vis, 0, sizeof(vis));
    queue.clear();
}

六、高频模拟题模板代码

模板1:日期模拟(求星期几)

int days[] = {0,31,28,31,30,31,30,31,31,30,31,30,31};

bool isLeap(int y) {
    return (y%4==0 && y%100!=0) || (y%400==0);
}

// 计算y-m-d是当年的第几天
int dayOfYear(int y, int m, int d) {
    int sum = d;
    for (int i = 1; i < m; i++) {
        sum += days[i];
        if (i==2 && isLeap(y)) sum++;
    }
    return sum;
}

// 计算两个日期差多少天
int dateDiff(int y1, int m1, int d1, int y2, int m2, int d2) {
    // 从公元1年1月1日开始累加
    // 或直接用循环模拟
}

模板2:队列模拟(排队问题)

queue<int> q;  // 排队队列
while (!gameOver) {
    int cur = q.front(); q.pop();
    // 处理cur
    q.push(cur);  // 下一轮继续
}

模板3:投票模拟

int vote[N];  // 每个候选人票数
while (cin >> x) {
    if (x == -1) break;  // 结束
    vote[x]++;
}
// 找最大值
int winner = 0;
for (int i = 1; i <= n; i++)
    if (vote[i] > vote[winner]) winner = i;

七、调试技巧(模拟题救命稻草)

  1. 打印每一轮状态

for (int round = 1; round <= 100; round++) {
    // 处理逻辑
    if (round <= 10) {  // 只打印前10轮,避免输出太多
        cout << "Round " << round << ": ";
        // 打印关键变量
    }
}
  1. 用assert断言

#include <cassert>
assert(x >= 1 && x <= n);  // 若条件不满足,程序立即终止
  1. 对拍法:写暴力解法和你认为正确的解法,随机小数据对拍


八、CSP-J模拟题速查表

题目类型关键状态循环结构复杂度
流程模拟步骤计数器while(有输入)O(n)
字符串模拟当前位置指针for(每个字符)O(len)
日期模拟年月日变量while(未到目标)O(天数)
游戏模拟棋盘状态数组while(!结束)O(步数)

九、一句话总结

CSP-J模拟题 = 读懂规则 + 定义状态变量 + while循环 + if分支,核心是细节不出错,边界要特判,状态要初始化。

### 问题分析 在 NOIP2010 普及组的“接水问题”中,任务是模拟多个学生排队接水的过程。每个学生的接水时间不同,而只有有限数量的水龙头可用。新加入的学生会选择当前总耗时最短的队列进行排队。最终需要计算所有学生完成接水的总时间。 为了实现这一逻辑,可以使用结构体来存储每列水龙头的状态,包括该列当前的总耗时和学生编号等信息。通过排序或遍历找到最短队列,并将新的学生分配到该队列中[^3]。 ### C++ 结构体实现方法 定义一个结构体 `Tap` 来表示每个水龙头的状态: ```cpp struct Tap { int total_time; // 当前队列的总耗时 int id; // 队列编号(可选) // 初始化函数 void init() { total_time = 0; } }; ``` #### 主要步骤如下: 1. **输入处理**:读取学生人数 `n` 和水龙头数量 `m`。 2. **初始化水龙头数组**:创建大小为 `m` 的 `Tap` 数组,并对每个元素调用 `init()` 方法。 3. **分配学生到队列**:对于每个学生的接水时间,遍历水龙头数组,找出当前总耗时最小的队列。 4. **更新队列状态**:将该学生的接水时间加到对应队列的 `total_time` 中。 5. **记录最大值**:在每次更新后,比较当前最大值并保留最大的那个。 6. **输出结果**:最后输出最大值作为所有学生完成接水的时间。 #### 完整代码示例 ```cpp #include <iostream> #include <climits> using namespace std; struct Tap { int total_time; void init() { total_time = 0; } }; int main() { int n, m; cin >> n >> m; Tap taps[100]; // 假设最多100个水龙头 for (int i = 0; i < m; ++i) { taps[i].init(); } int max_time = 0; for (int i = 0; i < n; ++i) { int time; cin >> time; // 找到当前总耗时最小的水龙头 int min_index = 0; for (int j = 1; j < m; ++j) { if (taps[j].total_time < taps[min_index].total_time) { min_index = j; } } // 更新队列时间 taps[min_index].total_time += time; // 更新最大时间 if (taps[min_index].total_time > max_time) { max_time = taps[min_index].total_time; } } cout << max_time << endl; return 0; } ``` ### 总结 该方法利用了结构体来封装每个水龙头的信息,并通过循环查找最短队列的方式实现任务分配。这种方法在时间和空间上都较为高效,适用于题目要求的数据规模。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值