2021 RoboCom 世界机器人开发者大赛-本科组(复赛)

1废话

因为某种原因要写这个题解QAQ(难道是我摸鱼太久被老师发现了?

写完了没人看QAQ

本菜狗博客园博客呐,博客园没人看QAQ(一个星期不到20人看,其中还有几个看估计是用爬虫把我文章爬走到其他网站的QAQ),所以我搬运到我的本菜狗csdn博客了,实话说csdn博客真的没博客园好玩(我这次markdown代码复制过来的还要稍微修改,而且还不能像博客园那样修改美化自己的博客)

2题解

7-1 冒险者分队 (20 分)

题意:

输入正整数T,代表询问次数,每次询问2行,每行3个数,第一行表示初始数据,第二行为目标数据,数据变化为2种:1.一种数据提高40,另外两种数据降低20 2.两种数据提高20,另外一组数据降低40 ,求初始数据到目标数据数据变化的最少次数

思路:

看数据范围,输入的T大小小于 1 0 5 10^5 105,这个数据有点大,最好用scanf或者优化后的cin等来输入来优化时间(虽然说好像直接用cin也没事),所有属性值大于等于0,小于等于 2 × 1 0 9 2×10^9 2×109,这个用int也行,int的数据刚好比 2 × 1 0 9 2×10^9 2×109大一些,如果不放心用long long也行。

然后来判断非法情况:1.各种数据的差模上20的值不为0 2.数据和不相等 3.(这个可能不明显)我们观察这数据变化,+40 -20 -20 和+20 +20 -40(其实也可以先除以20来方便看),我们会发现这数值加60(防止负数模上一个数)再模上60的值都是一样的,于是初始数据与目标数据的差模去60的值都是一样的

最后来考虑最优解(因为题目要求的是数据变化的最小次数),首先把各个值的差x1,x2,x3排序设x1<=x2<=x3,我们可以分为3种情况x1<0,x1=x2=x3,其中x1=x2=x3为0,前面2种情况可以先全换成x1<=x2<0

代码:

#include
#include//sort函数
using namespace std;
#define endl  "\n"//加快cout输出换行速度的
//#define int long long //想用long long的话把这行注释取消就行
signed main()//用signed是因为不存在long long main
{
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);//加快cin和cout的速度
    int t;
    cin>>t;
    while(t--)
    {
        int a,b,c,x,y,z;
        cin>>a>>b>>c>>x>>y>>z;
        //初始数据和目标数据相等直接输出0
        if(a==x&&b==y&&c==z)
        {
            cout<<0<<endl;
            continue;
        }

        //接下来判断非法情况
        if((a-x)%20!=0||(b-y)%20!=0||(c-z)%20!=0)//差值不为20的倍数
        {
            cout<<-1<<endl;
            continue;
        }
        if((a+b+c)!=(x+y+z))//由2种方法可知,数据和不变
        {
            cout<<-1<<endl;
            continue;
        }
        //if(((a-x)%60+60)%60!=((b-y)%60+60)%60||((a-x)%60+60)%60!=((c-z)%60+60)%60)
        //(这个可能不明显)我们观察这数据变化,+40 -20 -20 和+20 +20 -40(其实也可以先除以20来方便看)
        //我们会发现这数值加60(防止结果为负数)再模上60的值都是一样的,于是初始数据与目标数据的差模去60的值都是一样的
        int ax=(a-x)/20,by=(b-y)/20,cz=(c-z)/20;
        if((ax%3+3)%3!=(by%3+3)%3||(ax%3+3)%3!=(cz%3+3)%3)
        {
            cout<<-1<<endl;
            continue;
        }


        //考虑最优解
        //首先把各个值的差x1,x2,x3排序设x1<=x2<=x3,我们可以分为2种情况x1<0
        //x1=x2=x3=0的情况最开始已经排除了
        //为了一次解决2种情况,我们可以考虑把情况x1<0
        //以x1<=x2<0
        //之后得到x1<=0<=x3,之后再用(+60,0,-60)即(+20+40,+20-20,-40-20)来使得x1=0=x3
        //注:最后x1和x3最后肯定能被60整除,由上非法情况可知,差模60的值相等,且当x2=0时,x1和x3模60也一定为0
        //这样x1=x2=x3=0,即完成了初始数据到目标数据的转换
        int num=0;//用来记录大于0的个数
        num=ax>0+by>0+cz>0;
        if(num==2)
        ax=-ax,by=-by,cz=-cz;
        int f[3]={ax,by,cz};
        sort(f,f+3);//其实就是我懒得慢慢排序
        int ans=0;//用来记录答案
        ans+=-f[1];//把x2变成0后需要的次数
        f[0]=f[0]-f[1],f[2]=f[2]+2*f[1];//x2变成0后其他数的变化
        ans+=f[2]/3*2;//把x3变成0的需要次数,/3是因为60是20的3倍,*2是因为(+60,0,-60)要2步来完成
        cout<<ans<<endl;
    }
    return 0;
}

7-2 拼题A打卡奖励 (25 分)

题意:

给定 N张打卡卷,第 i 张打卡卷需要 mi 分钟做完,完成后可获得 ci 枚奖励的金币。问在M 分钟内最多可以得到多少金币。

思路:

显然为01背包问题,把时间看成背包问题中的重量w,把奖励金币看成背包问题中的价值v,求在给定的w中取得的最大v。

首先分析时间复杂度,在pta中c/c++的时间限制为400ms,按照一般计算机计算的速度1s为 1 0 8 10^8 108来算的话,此题的计算量应该不能大于4× 1 0 7 10^7 107,但是按照常规的01背包来算的话,时间复杂度为O(NM)(其中N在背包问题中是物品的个数,在此题中N<= 1 0 3 10^3 103,M在背包问题中是背包的最大容积,在此题中M的范围为365×24×60,约为 1 0 5 10^5 105,于是O(NM)的时间复杂度约为 1 0 8 10^8 108,大于4× 1 0 7 10^7 107)通过分析时间复杂度得知用常规的01背包来码的话就会超时,故我们应该换一种方法。

我们再观察题目所给的其他数据,ci<30 成为题目转化的一个突破口。按照原来做法就是用最小容量求最大价值,现在反过来用最大价值求最小容量。最大价值:1000∗30, 复杂度:1000∗30∗1000为3× 1 0 7 10^7 107小于4× 1 0 7 10^7 107,故可以用该方法 。

代码:

#include
#include//min函数
#include//memset函数
using namespace std;
int w[1010],v[1010];//其中N的范围为1e3
int dp[30010];//最大金币数量N*ci,1000*30
//其中dp函数的的意思是:dp下标为金币的数量(价值),dp下标所对应的值为得到该数量的金币所需要的最小时间(重量)
signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);//加快cin和cout的速度
    int n,m,sum=0;//sum为金币数量和,即最大的价值
    cin>>n>>m;
    for(int i=1;i<=n;i++)cin>>w[i];
    for(int i=1;i<=n;i++)cin>>v[i],sum+=v[i];
    //读入数据
    memset(dp,0x3f,sizeof dp);//把dp中每个元素初始化为一个特别大的值
    dp[0]=0;//用来作为边界,在0价值的时候用掉0的时间
    for(int i=1;i<=n;i++)
        for(int j=sum;j>=v[i];j--)
            dp[j]=min(dp[j],dp[j-v[i]]+w[i]);
    //类似01背包空间压缩的常规写法,不过w和v的位置对调了,而且不是求max而是求min
    int ans=0;//ans为找到dp下标的值,且dp下标所对应的值小于或者等于m的值
    for(int i = sum; i >= 0; i--)//从最大的金币数量开始往下找
        if(dp[i] <= m) 
            {
                ans=i;
                break;
            }//找到了就用ans标记,并且跳出循环
    cout<<ans;
    return 0;
}

7-3快递装箱(25分)

题意:

具体还是直接看题目吧,我说的也不太好QAQ。一共ABCD四个口,12两缸摄像头,快递从A口开始,到B口如果是快递且重量大于W1装箱,到C口检查箱子,大于W2装车(此时1号摄像头拍摄,装车后的快递不用考虑了),其他的进入D进行装箱(分2情况,1:D空:等下一快递;2:D不空:考虑下一快递是否装得下来进行新快递装入或者装箱,如果下一是箱子或没货物,则检查重量来进行放A口或者装车(此时2号摄像头拍摄),在A口的箱子要等汇合到A的快递,如果可以装入就装入后走B,不可以就等待。

输出12摄像头拍摄次数和剩下箱子数目,并输出重量,数目如果为0的话输出None。

思路:

就是塞个货物进传送带,然后动一动,更新一下每个点的状态,如果有货物运出就答案加一。A、D 点用了双端队列处理(因为有塞回去的情况),B、C用了普通队列处理。每次更新按题目要求处理,用一个 pair 存储货物状态,分别记录重量和是否装箱。

然后塞完货物之后再让传送带动一动把一些该送走而没送走的东西处理一下,最后把传送带剩下的箱子拿出来看看。

代码来源:https://gist.github.com/dailongao/b310025a2b61b210f7822e1a3054ee8f

代码:

#include 
#include //queue函数
#include //deque函数
#include //vector函数
#include //sort函数
using namespace std;
typedef pair<int, int> PII;
queue<PII> B, C, D2;
deque<PII> A, D;
int wmax, w1, w2, N;
int ans1 = 0, ans2 = 0;//ans1记录被1号摄像头拍摄次数,ans2记录被2号摄像头拍摄次数
vector<int> ans;//用来储存最后剩下的货物重量

void pushA(int v) {
    if(A.size() == 0) {
        A.push_back(make_pair(v, 0));
        //make_pair(v,0)和pair(v,0)基本上是等价的,就是给一个pair赋值
    } else {
        PII x = A.front();
        if(x.first + v <= wmax) {
            A.pop_front();
            A.push_front(make_pair(x.first + v, 1));
        } else {
            A.push_front(make_pair(v, 0));
        }
    }
}

void update() {
    if(D.size() != 0) {
        PII x = D.front();
        if(x.second == 0) {
            D.pop_front();
            D.push_front(make_pair(x.first, 1));
        } else {
            D.pop_front();
            if(D.size() == 0) {
                D.push_front(x);
            } else {
                PII y = D.front();
                if(y.second == 0) {
                    if(y.first + x.first <= wmax) {
                        D.pop_front();
                        D.push_front(make_pair(x.first + y.first, 1));
                    } else {
                        if(x.first > w2) {
                            ans2++;
                        } else {
                            A.push_back(x);
                        }
                    }
                } else {
                    if(x.first > w2) {
                        ans2++;
                    } else {
                        A.push_back(x);
                    }
                }
                if(D.front().second == 0) {
                    PII z = D.front();
                    D.pop_front();
                    D.push_front(make_pair(z.first, 1));
                }
            }
        }
    }
    
    if(C.size() != 0) {
        PII x = C.front();
        C.pop();
        if(x.first > w2) {
            ans1++;
        } else {
            D.push_back(x);
        }
    }

    if(B.size() != 0) {
        PII x = B.front();
        B.pop();
        if(x.first > w1) {
            C.push(make_pair(x.first, 1));
        } else {
            C.push(x);
        }
    }

    if(A.size() != 0) {
        B.push(A.front());
        A.pop_front();
    }
}

int main() {
    scanf("%d%d%d%d", &N, &wmax, &w1, &w2);
    for(int i = 0; i < N; ++i) {
        int x;
        scanf("%d", &x);
        pushA(x);//把x放到A口
        update();//动一下,进行一次更新
    }
    for(int i = 0; i < 10005; ++i) {//一直更新(次数只能多不能少)
        update();
    }
    while(D.size() != 0) {
        PII x = D.front();
        D.pop_front();
        if(x.first > w2)
            ans2++;
        else {
            D2.push(x);
        }
    }
    while(D2.size() != 0) {
        D.push_back(D2.front());
        D2.pop();
    }

    while(A.size() != 0) {
        if(A.front().first != 0) ans.push_back(A.front().first);
        A.pop_front();
    }
    while(B.size() != 0) {
        if(B.front().first != 0) ans.push_back(B.front().first);
        B.pop();
    }
    while(C.size() != 0) {
        if(C.front().first != 0) ans.push_back(C.front().first);
        C.pop();
    }
    while(D.size() != 0) {
        if(D.front().first != 0) ans.push_back(D.front().first);
        D.pop_front();
    }
    //题目要求:第二行按非递减序输出剩下的箱子的重量
    sort(ans.begin(), ans.end());
    
    printf("%d %d %d\n", ans1, ans2, ans.size());
    if(ans.size() == 0) puts("None");
    else {
        for(int i = 0; i < ans.size(); ++i)
            printf("%d%c", ans[i], i == ans.size() - 1 ? '\n' : ' ');
    }
}

7-4塔防游戏(30分)

题意:

输入n 和 m,为地图的尺寸, T,为一轮攻击持续的时长。

随后给出 n+2 行,每行给出 m+2 个数字,每行中的数字都用空格分隔,表示攻击开始前地图上的布局。其中第 1 行、第 1 列、第 n+2 行、第 m+2 列是地图边界外僵尸们出发的位置,这些位置上,0 表示没有僵尸,其他正整数表示从该位置出发的僵尸们的数量。而地图中的每个位置上,0 表示没有堡垒,其它正整数表示该位置上堡垒的防御能力值。大本营是一个负数 −D ,其防御能力值为 D。所有的僵尸队都会根据进攻开始时的地图选择被歼灭最少的到达大本营的路线,并且一直按照这个路线行进,

输出 n 行,每行 m 个数字,对应攻击结束后地图上每个方格的状态。

如果大本营被攻陷还要输出Game Over

思路:

首先因为起点不一而终点一致,我们可以做从起点出发的最短路,确定每队僵尸的行走路线。最短路因为是网格图,建议还是用带优先队列的 Dijkstra,不容易被卡复杂度(我们有空的时候会讲讲各种最短路算法的最坏情况)。

然后按照时间移动僵尸即可。我的做法是直接维护僵尸队在的位置、每次扫一次所有僵尸队伍,对于还活着的僵尸队伍,尝试向最短路路径上的下一个点移动一步,如果有堡垒挡住就打一下堡垒即可。

注意:当3个队同时攻打某个防御值为2的堡垒,该堡垒消失的同时,3个队都会减员1人,也可以理解成 ”堡垒可能减为负值“ 。最外围点(僵尸老窝)之间互不相通。

详细的还是看代码吧

代码来源:7-4 塔防游戏 (30 分)

另:https://gist.github.com/dailongao/b26364f4d6f876bdb79d1045eaf85500

代码:

#include 
#include 
#include 
#include 
#include 
#define x first
#define y second
using namespace std;
typedef pair<int,int>PII;
typedef pair<int,PII>PIII;
struct Node{
    int x , y , cc ;
};
const int N = 200;
int f[N][N];//地图
int n,m,t;
int dx[] = {-1,1,0,0},dy[] = {0,0,-1,1};//方向
PII e ;//用来记录大本营坐标
vector<Node>v;//用来记录僵尸
int dis[N][N];//用来记录经过防御塔的点数,即消耗量
int vis[N][N];//用来记录接下来是否经历过该坐标
int tim[N][N];//用来记录走过的步数
PII path[N][N];//用来记录路径
map<pair<int,int>,int>st ;//
int idx = 1  ;//用来计数
int cnt[N*N];//僵尸个数
int now[N*N];
vector<PII>p[N*N];//记录每个坐标僵尸的路线
int get(PII xx)
{
    if(st.count(xx) == 0) st[xx] = idx++ ;
    return st[xx] ;
}
void solve(){
    //输入数据
    scanf("%d %d %d",&n,&m,&t);
    for(int i=0;i<=n+1;i++)
        for(int j=0;j<=m+1;j++)
        {
            vis[i][j] = false;//每个点没经过
            dis[i][j] = 0x3f3f3f3f;//把消耗设置为最大值
            path[i][j] = {-1,-1};//把路径先弄成-1
            tim[i][j] = 0 ;//经过的格子先设置为0
            scanf("%d",&f[i][j]);
            if(f[i][j]<0)e = {i,j};//记录大本营坐标
            if((i==0&&j==m+1)||(i==0&&j==0)||(i==n+1&&j==0)||(i==n+1&&j==m+1))//4个角落的数据不用管
                continue;
            if(i < 1 || i > n || j < 1 || j > m && f[i][j] > 0)//把僵尸放进一个向量v里
                v.push_back({i,j,f[i][j]});
        }
    priority_queue<PIII,vector<PIII>,greater<PIII>>q;//优先队列,自动从小到大排序(排序第一个元素)
    //用带优先队列的 Dijkstra,不容易被卡复杂度
    dis[e.x][e.y] = 0;
    q.push({0,{e.x,e.y}});//以大本营位置开始往四周位置广搜,其中第一个为消耗,后面的为坐标
    while(!q.empty()){
        auto g = q.top();q.pop();
        PII l = g.y;
        if(vis[l.x][l.y])continue;//如果遍历过之后就不用再考虑了
        vis[l.x][l.y] = true;
        for(int i=0;i<4;i++){
            int a = l.x+dx[i] , b = l.y+dy[i];
            if(a < 1 || a > n  || b < 1 || b > m ) continue;//非法情况
            if(dis[a][b] > dis[l.x][l.y] + f[a][b]){//如果找到到达该点消耗更小的,则进行更新
                dis[a][b] = dis[l.x][l.y] + f[a][b];
                path[a][b] =  l;
                tim[a][b] = tim[l.x][l.y]+1;
            }
            else if(dis[a][b]==dis[l.x][l.y]+f[a][b]){//由题意可知,如果有消耗相等的,则找到到达大本营最快的
                if(tim[a][b]>tim[l.x][l.y]+1){
                    tim[a][b] = tim[l.x][l.y]+1;
                    path[a][b] = l;
                }
            }
            q.push({dis[a][b],{a,b}});
        }
    }
    for(auto it:v){//遍历向量v
        int id = get({it.x,it.y});
        cnt[id] = it.cc ;
        PII kk ;
        for(int i = 0 ; i < 4 ; i++) {//由僵尸边上走到地图里面去
            if(it.x + dx[i] >= 1 && it.x + dx[i] <= n && it.y + dy[i] >= 1 && it.y + dy[i] <= m){
                kk.x = it.x + dx[i] , kk.y = it.y  + dy[i];
                break ;
            }
        }
        while(true){
            p[id].push_back(kk);
            if(kk.x==e.x && kk.y == e.y)break;//找到大本营的位置了
            kk = path[kk.x][kk.y];
        }
    }
    //考虑每段时间的变化
    for(int i=1;i<=t;i++){
        set<pair<int,int>>ss;
        for(auto it:v){
            int no = get({it.x,it.y});
            if(cnt[no]==0)continue;
            int x = p[no][now[no]].x,y = p[no][now[no]].y;
            if(f[x][y] > 0 && !(x == e.x && y == e.y)){
                ss.insert({x,y});
            }else if(f[x][y] < 0 && (x == e.x && y == e.y)){
                ss.insert({x,y});
            }
        }
        for(auto it:v){
            int no = get({it.x,it.y});
            if(cnt[no]==0)continue;
            int x = p[no][now[no]].x,y = p[no][now[no]].y;
            if(ss.count({x,y}) == 0){
                now[no] = min(now[no]+1,(int)p[no].size()-1);
            }else{//对防御塔和大本营进行进攻
                if(f[x][y] > 0) f[x][y]--;//防御塔
                else if(f[x][y] < 0) f[x][y]++;//大本营
                cnt[no]--;//僵尸数量减少
            }
        }
        if(f[e.x][e.y] == 0){//如果大本营没血了gameover了
            break;
        }
    }
    //输出
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            printf("%d",f[i][j]);
            if(j<m)printf(" ");//保证最后一格没有空格
        }
        printf("\n");
    }
    if(f[e.x][e.y]==0)printf("Game Over");
}
signed main() {
    solve();
    return 0;
}

你可能感兴趣的:(机器人大赛,c++)