复杂模拟综合
- 例题
- 1. 玩具谜题
- 2. 猴子兄弟爬山
- 3. 浇水
- 4. 数组旋转
- 5. 石头剪刀布
- 6. 巨石滚滚
例题
1. 玩具谜题
小南有一套可爱的玩具小人,它们各有不同的职业。
有一天,这些玩具小人把小南的眼镜藏了起来。 小南发现玩具小人们围成了一个圈,它们有的面朝圈内,有的面朝圈外。如下图:
这时 singer 告诉小南一个谜题:“眼镜藏在我左数第
3
3
3 个玩具小人的右数第
1
1
1 个玩具小人的左数第
2
2
2 个玩具小人那里。”
小南发现,这个谜题中玩具小人的朝向非常关键,因为朝内和朝外的玩具小人的左右方向是相反的:面朝圈内的玩具小人,它的左边是顺时针方向,右边是逆时针方向;而面向圈外的玩具小人,它的左边是逆时针方向,右边是顺时针方向。
小南一边艰难地辨认着玩具小人,一边数着:
singer 朝内,左数第
3
3
3 个是 archer;
archer 朝外,右数第
1
1
1 个是 thinker;
thinker 朝外,左数第
2
2
2 个是 writer。
所以眼睛藏在 writer 里。
虽然成功找回了眼镜,但小南并没有放心。 如果下次有更多的玩具小人藏他的眼镜,或是谜題的长度更长,他可能就无法找到眼镜了。所以小南希望你写程序帮他解决类似的谜題。这样的谜題具体可以描述为:
有
n
n
n 个玩具小人围成一圈,已知它们的职业和朝向。现在第
1
1
1 个玩具小人告诉小南一个包含
m
m
m 条指令的谜題,其中第
z
z
z 条指令形如"左数/右数第
s
s
s 个玩具小人"。你需要输出依次数完这些指令后,到达的玩具小人的职业。
答题技巧:
- 遇到循环的圈,数组尽量从 0 0 0 编号(只要对下标操作,就从 0 0 0 存)
- 尽量不要在结构体里面定义字符串,可能会有错误(
malloc
分配空间的错误) - 遇到循环的圈,在更新下标的时候一定要记得加上 n n n 在模 n n n(考虑负数的情况)
参考答案:
#include<bits/stdc++.h>
using namespace std;
struct Node{
int a;
char s[105];
}a[100010];
int n,m,d,s,idx;
int main(){
cin>>n>>m;
for(int i=0;i<n;i++)cin>>a[i].a>>a[i].s;
while(m--){
cin>>d>>s;
if(d==0){
if(a[idx].a==0)idx=(idx-s+n)%n;
else idx=(idx+s)%n;
}else{
if(a[idx].a==0)idx=(idx+s)%n;
else idx=(idx-s+n)%n;
}
}
cout<<a[idx].s;
return 0;
}
2. 猴子兄弟爬山
已知皮皮和大智是关系非常友好的两只猴子,并且它们都居住在同一座山上。山高
h
h
h 米,皮皮在距离山脚
h
a
h_a
ha 米的地方居住,大智在距离山脚
h
b
h_b
hb 米的地方居住。
皮皮和大智相约爬山,它们约定从同一天的白天开始从自己居住的地方开始往山顶爬,每天皮皮和大智都会根据自己的实际情况决定自己的行为:
- 白天正常情况下,皮皮每天爬 u a u_a ua 米,大智每天爬 u b u_b ub 米,但是如果白天开始时对方比自己高,那么皮皮会多爬 a d d a add_a adda 米,大智会多爬 a d d b add_b addb 米。
- 黑夜正常情况下,皮皮每天掉 d a d_a da 米,大智每天掉 d b d_b db 米,但是如果黑夜开始时对方比自己高,那么皮皮会少掉 s u b a sub_a suba 米,大智会少掉 s u b b sub_b subb 米。
现在请你帮助计算皮皮和大智两只猴子都爬到山顶所需要的时间(天数),你需要回答 t t t 个这样的问题。数据数据保证两人一定能够在有限步数内登上山顶。
答题技巧:
- 分段、贪心,努力逼近最优解
- 注意特殊判断,及时终止(也可以设极端值实现)
参考答案:
#include<bits/stdc++.h>
using namespace std;
int t,h,ha,hb,ua,ub,adda,addb,da,db,suba,subb;
int main(){
cin>>t;
while(t--){
cin>>h>>ha>>hb>>ua>>ub>>adda>>addb>>da>>db>>suba>>subb;
int day=0;
while(ha<h||hb<h){
day++;
if(ha>hb)hb+=addb;
else if(hb>ha)ha+=adda;
ha+=ua,hb+=ub;
if(ha>=h)ha=1e9;
if(hb>=h)hb=1e9;
if(ha>hb)hb+=subb;
else if(hb>ha)ha+=suba;
ha-=da,hb-=db;
}
cout<<day<<endl;
}
return 0;
}
3. 浇水
皮皮和小美打算给花园里的
n
n
n 株植物浇水。
植物排成一行,编号从左到右依次从
1
∼
n
1\sim n
1∼n。其中,第
i
(
1
≤
i
≤
n
)
i(1≤i≤n)
i(1≤i≤n) 株植物的位置
x
=
i
x=i
x=i,需要浇的水量为
w
i
w_i
wi 升。皮皮和小美每人有一个水罐,其中皮皮的水罐容量为
A
A
A 升,小美的水罐的容量为
B
B
B 升,初始时,两个水罐都装满了水。
皮皮从左到右给植物浇水,从第
1
1
1 株植物开始。小美从右到左给植物浇水,从第
n
n
n 株植物开始。他们俩同时开始给植物浇水。
皮皮和小美各自为每株植物浇水时,每分钟浇一升水,也就是每过去一分钟,水罐中的水减少一升,植物还需要浇的水也减少一升。如果当前植物所需浇水量减为零,就移动到下一棵植物(对于皮皮是编号大一的植物,对于小美是编号小一的植物),移动的时间忽略不计。
如果浇水途中水罐中的水耗尽了,一个超强水泵会用
T
T
T 分钟的时间,重新灌满水罐,然后重新开始浇水。
如果某人到达一株植物时,另一个人已经先到了,那么就只让先到的人浇水。如果皮皮和小美同时到达,就让皮皮完成浇水。
请你实现一个程序,求为所有植物完成浇水的时间。
参考答案:
//超时版错因: 程序模拟了时间,但是10^11绝对超
#include<bits/stdc++.h>
using namespace std;
int n,A,B,T,w[100010];
long long l,r,a,b,ta,tb;
int main(){
cin>>n>>A>>B>>T;
for(int i=1;i<=n;i++)cin>>w[i];
l=1,r=n,a=A,b=B;
while(l<=r){
if(ta<=tb){
while(w[l]){
if(a==0){
a=A;
ta+=T;
}
w[l]--,a--,ta++;
}
l++;
}else{
while(w[r]){
if(b==0){
b=B;
tb+=T;
}
w[r]--,b--,tb++;
}
r--;
}
}
cout<<max(ta,tb);
return 0;
}
技巧:
- 解耦法
- 注意
ceil
的精度问题
AC 代码:
#include<bits/stdc++.h>
using namespace std;
int n,A,B,T,w[100010];
long long l,r,a,b,ta,tb;
void nextA(){
if(a>=w[l])
a-=w[l],ta+=w[l];
else{
int cnt=ceil((w[l]-a)*1.0/A);
ta+=w[l]+1ll*cnt*T,a=((a-w[l])%A+A)%A;
}
l++;
}
void nextB(){
if(b>=w[r])
b-=w[r],tb+=w[r];
else{
int cnt=(w[r]-b-1)/B+1;
tb+=w[r]+1ll*cnt*T,b=((b-w[r])%B+B)%B;
}
r--;
}
int main(){
cin>>n>>A>>B>>T;
for(int i=1;i<=n;i++)cin>>w[i];
l=1,r=n,a=A,b=B;
while(l<=r){
if(ta<=tb)nextA();
else nextB();
}
cout<<max(ta,tb);
return 0;
}
4. 数组旋转
给定一个整数数组 < a 1 , a 2 , a 3 , ⋯ , a n > <a_1,a_2,a_3,\cdots,a_n> <a1,a2,a3,⋯,an>,然后输入一系列数字 < x 1 , x 2 , x 3 , ⋯ , x m > <x_1,x_2,x_3,\cdots,x_m> <x1,x2,x3,⋯,xm>,如果 x i > 0 x_i>0 xi>0,代表将数组中的元素向右旋转 x i x_i xi 个位置。如果 x i < 0 x_i<0 xi<0,代表将数组中的元素向左旋转 − x i -x_i −xi 个位置。
技巧:向后补充,a[i+n]=a[i]
但是——暴力 YYDS!
#include<bits/stdc++.h>
using namespace std;
struct Node{
int val,idx;
}a[1005];
int n,m,x;
bool cmp(Node a, Node b){
return a.idx<b.idx;
}
int main(){
cin>>n;
for(int i=0;i<n;i++){
cin>>a[i].val;
a[i].idx=i;
}
cin>>m;
while(m--){
cin>>x;
if(x>0){
for(int i=0;i<n;i++)
a[i].idx=(a[i].idx+x)%n;
}else{
for(int i=0;i<n;i++)
a[i].idx=((a[i].idx-abs(x))%n+n)%n;
}
sort(a,a+n,cmp);
for(int i=0;i<n;i++)cout<<a[i].val<<" ";
cout<<endl;
}
return 0;
}
5. 石头剪刀布
石头剪子布,是一种猜拳游戏。起源于中国,然后传到日本、朝鲜等地,随着亚欧贸易的不断发展它传到了欧洲,到了近现代逐渐风靡世界。简单明了的规则,使得石头剪子布没有任何规则漏洞可钻,单次玩法比拼运气,多回合玩法比拼心理博弈,使得石头剪子布这个古老的游戏同时用于“意外”与“技术”两种特性,深受世界人民喜爱。
游戏规则:石头打剪刀(用 G 表示石头),布包石头(用 C 表示布),剪刀剪布(用 P 表示剪刀)。
现在有
2
n
2n
2n 名选手进行两两猜拳,一共进行
m
m
m 轮,选手编号为
1
∼
2
n
1\sim 2n
1∼2n,初始时,所有选手的排名按照编号从小到大排列。小猴有预知未来的能力,知道第
j
j
j 轮中编号为
i
i
i 的选手出结果为
g
i
,
j
g_{i,j}
gi,j,其中
g
i
,
j
g_{i,j}
gi,j 为 G、C、P 三者之一。
每轮比赛中共会进行
n
n
n 场比赛,都是让最新的排名(按照分数从高到低排序,如果有分数相同的情况,则按编号从小到大排序)中的第
1
1
1 名与第
2
2
2 名比赛(第一场比赛)、第
3
3
3 名与第
4
4
4 名比赛(第二场比赛)、
…
…、第
2
k
−
1
2k−1
2k−1 名与第
2
k
2k
2k 名比赛(第
k
k
k 场比赛,其中
1
≤
k
≤
n
1≤k≤n
1≤k≤n)。
每场比赛中的结果均为:
一名选手获胜,另一名选手输,获胜方选手得
1
1
1 分,输得一方选手不得分;
双方平局,双方选手均不得分。
小猴因为使用超能力预知每名选手出的结果,现在需要休息,所以请你计算并输出第
m
m
m 轮结束时所有人的排名,即按照分数从高到低输出每名选手的编号,如果有分数相同的情况,则优先输出编号更小的选手。
技巧:重载运算符。
#include<bits/stdc++.h>
using namespace std;
struct Node{
int id,score;
bool operator<(const Node& rhs) const {
if(score!=rhs.score)return score>rhs.score;
return id<rhs.id;
}
}a[2010];
int n,m;
char g[2010][2010];
bool check(char a,char b){
if(a=='G'&&b=='P')return 1;
if(a=='C'&&b=='G')return 1;
if(a=='P'&&b=='C')return 1;
return 0;
}
int main(){
cin>>n>>m;
n*=2;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++)
cin>>g[i][j];
a[i].id=i;
}
for(int j=1;j<=m;j++){
for(int i=1;i<=n;i+=2){
int x=a[i].id,y=a[i+1].id;
a[i].score+=check(g[x][j],g[y][j]);
a[i+1].score+=check(g[y][j],g[x][j]);
}
sort(a+1,a+n+1);
}
for(int i=1;i<=n;i++)cout<<a[i].id<<endl;
return 0;
}
6. 巨石滚滚
小猴最近在一款非常火热的游戏“巨石滚滚”,游戏中某一个关卡难住了小猴。具体来说,该关卡
给定一个
n
×
m
n×m
n×m 的矩阵,矩阵中包含三种类型的单元格:
- 空单元格,用 . 表示;
- 一块巨石,用 * 表示;
- 一个障碍物,用 # 表示。
游戏开始时,所有巨石都会掉落下来(第一行最高,第
n
n
n 行最低),直到碰到地板(第
n
n
n 行)、障碍物或其他已经无法移动的巨石才会停止掉落。
现在给定矩阵中每个单元格的初始状态,即每个单元格可能为空、有一块巨石或有一个障碍物,请你帮助小猴计算矩阵中所有单元格的最终状态。
//超时代码
#include<bits/stdc++.h>
using namespace std;
int n,m;
char a[2010][2010];
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
cin>>a[i][j];
for(int i=n-1;i>=1;i--)
for(int j=1;j<=m;j++)
if(a[i][j]=='*'){
int t=i,idx=i;
while(idx<n&&a[idx+1][j]=='.')idx++;
a[t][j]='.',a[idx][j]='*';
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++)
cout<<a[i][j];
cout<<endl;
}
return 0;
}
技巧:填坑法
AC 代码:
#include<bits/stdc++.h>
using namespace std;
int n,m;
char a[2010][2010];
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
cin>>a[i][j];
for(int j=1;j<=m;j++){
int down=n;
for(int i=n;i>=1;i--)
if(a[i][j]=='*')
swap(a[i][j],a[down--][j]);
else if(a[i][j]=='#')
down=i-1;
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++)
cout<<a[i][j];
cout<<endl;
}
return 0;
}