C++知识点总结(54):复杂模拟综合

例题

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 个玩具小人"。你需要输出依次数完这些指令后,到达的玩具小人的职业。

答题技巧:

  1. 遇到循环的圈,数组尽量从 0 0 0 编号(只要对下标操作,就从 0 0 0 存)
  2. 尽量不要在结构体里面定义字符串,可能会有错误(malloc 分配空间的错误)
  3. 遇到循环的圈,在更新下标的时候一定要记得加上 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 个这样的问题。数据数据保证两人一定能够在有限步数内登上山顶。

答题技巧:

  1. 分段、贪心,努力逼近最优解
  2. 注意特殊判断,及时终止(也可以设极端值实现)

参考答案:

#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 1n。其中,第 i ( 1 ≤ i ≤ n ) i(1≤i≤n) i(1in) 株植物的位置 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;
}

技巧:

  1. 解耦法
  2. 注意 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 12n,初始时,所有选手的排名按照编号从小到大排列。小猴有预知未来的能力,知道第 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 2k1 名与第 2 k 2k 2k 名比赛(第 k k k 场比赛,其中 1 ≤ k ≤ n 1≤k≤n 1kn)。
每场比赛中的结果均为:
一名选手获胜,另一名选手输,获胜方选手得 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;
}
  • 20
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值