2021 RoboCom 世界机器人开发者大赛-本科组(初赛)!带注释!题解

在这里插入图片描述

7-1 懂的都懂

众所周知,在互联网上有很多话是不好直接说出来的,不过一些模糊的图片仍然能让网友看懂你在说什么。然而对这种言论依然一定要出重拳,所以请你实现一个简单的匹配算法。

现在我们采集了原图的一些特征数据,由 N 个小于 255 的非负整数组成,假设对于给定的若干张由 Mi 个同样小于 255 的非负整数组成的新图的特征数据,每个数据都可以由原图中任意四个不同数据的平均值计算而来,则称新图为原图的相似图片。对于给出的数据,请你判断是不是相似图片。

注意,不同数据指的并非是数据的值不同,而是不能取同一个数据多次。对于两个相同值的数据,如果给出两次,则可以取两次。

输入格式

输入第一行是两个整数 N,K (1 ≤ N ≤ 50, 1 ≤ K ≤ 200),表示采集的原图的特征数据个数和新图的张数。

接下来一行为 N 个小于 255 的非负整数,表示原图的特征数据。

最后的 K 行,每行第一个数是 Mi (1 ≤ Mi ≤ 200),表示新图的特征数据个数。然后是 Mi个小于 255 的非负整数,表示新图的特征数据。

输出格式

对于每一张新图,如果为相似图片,则在一行中输出 Yes,否则输出 No。

输入样例

5 3
4 8 12 20 40
3 11 16 19
3 12 16 19
10 11 11 11 11 11 11 11 11 11 11

输出样例

Yes
No
Yes

解题思路

暴力枚举

注意:在计算可与原图对应的每个新图数据时,不能整除的数据不要存,因为不能整除代表原图中四个数据的平均值为小数,而新图中的数据一定为整数,二者不可能相等。若通过整除存于set,则无法通过测试点0。

改进:可不用set用unordered_set(哈希)

解题代码

#include<bits/stdc++.h>
using namespace std;
int a[202];
set<int> s;
int main()
{
    int n,m;
    cin>>n>>m;
    for(int i=0;i<n;i++) cin>>a[i];
    //四层遍历计算出可与原图对应的每个新图数据,存于set
    for(int i=0;i<n;i++)
        for(int j=i+1;j<n;j++)
            for(int k=j+1;k<n;k++)
                for(int l=k+1;l<n;l++)
                    if(!((a[i]+a[j]+a[k]+a[l])%4))//不能整除不存
                        s.insert((a[i]+a[j]+a[k]+a[l])/4);
    //检验新图中每个数据是否都在set中存在,否则不符合相似图片
    for(int i=0;i<m;i++){
        int t;
        cin>>t;
        bool f=1;
        for(int j=0;j<t;j++){
            int x;
            cin>>x;
            if(!s.count(x)) f=0;
        }
        if(f) cout<<"Yes"<<endl;
        else cout<<"No"<<endl;
    }
    return 0;
}

7-2 芬兰木棋

芬兰木棋(Mölkky,又称芬兰木柱)是源自芬兰的一项运动。哲哲将这个运动改造成了赛博朋克单人版,现在场上一开始有 N 根立起的小木棋(上面分别标有一个非负整数),哲哲投掷一根大木棋去击倒这些小木棋以获得分数。分数规则如下:

  • 如果仅击倒 1 根木棋,则得木棋上的分数。
  • 如果击倒 2 根或以上的木棋,则只得击倒根数的分数。(例如击倒 5 根,则得 5 分。)

哲哲固定站在 (0,0) 点上,四周放着若干个小木棋 (Xi,Yi),坐标均为整数。每次哲哲可以朝一个方向扔出大木棋,大木棋会打倒这个方向上离哲哲最近的 k 个小木棋。哲哲游戏水平很高超,所以这个 k 可以自由控制。

请问哲哲最多能拿多少分,在获得最多分数的情况下最少需要扔出多少次大木棋?

规则与真实规则有较大出入,真实游玩时请以国际莫尔基组织的规则为准

输入格式

输入第一行是一个正整数 N (1 ≤ N ≤ 105),表示场上一开始有 N 个木棋。

接下来 N 行,每行 3 个整数 Xi,Yi,Pi,分别表示木棋放置在 (Xi,Yi),木棋上的分数是 Pi。坐标在 32 位整数范围内,分数为小于等于 1000 的正整数。

保证 (0,0) 点没有木棋,也没有木棋重叠放置。

输出格式

输出一行两个数,表示最多分数以及获得最多分数最少需要投掷大木棋多少次。

输入样例

11
1 2 2
2 4 3
3 6 4
-1 2 2
-2 4 3
-3 6 4
-1 -2 1
-2 -4 1
-3 -6 1
-4 -8 2
2 -1 999

输出样例

1022 9

解题思路

首先,为拿到最多分数,总得分一定为所有木棋分数总和,不会出现失分,则需做到击倒分数大于1的木棋时k=1。

其次,为扔出最少次数大木棋,同方向的连续分数为1的木棋必须一次击倒,则需按斜率分类,对同方向的木棋按由近及远或由远及近的顺序储存(可用map映射),最后遍历判断。

解题代码

#include<bits/stdc++.h>
using namespace std;
struct node
{
    int x,y,p;
};
long long sum,cnt;
map<double,vector<node>> m;
bool cmp(node x,node y)//由近及远或由远及近顺序储存
{
    return x.y==y.y?x.x<=y.x:x.y<=y.y;
}
int main()
{
    int n;
    cin>>n;
    for(int i=0;i<n;i++){
        int x,y,p;
        cin>>x>>y>>p;
        sum+=p;//sum一定为所有木棋分数总和
        node no={x,y,p};
        m[y*1.0/x].push_back(no);//按斜率分类储存(未考虑x==0但已通过)
    }
    for(auto i:m){//斜率相同,即同方向
        vector<node> t=i.second;
        sort(t.begin(),t.end(),cmp);
        for(int j=0;j<t.size();j++){//按顺序遍历判断
            if(j==0||t[j].p!=1||t[j-1].p!=1) cnt++;
        }
    }
    cout<<sum<<' '<<cnt;
    return 0;
}

7-3 打怪升级

很多游戏都有打怪升级的环节,玩家需要打败一系列怪兽去赢取成就和徽章。这里我们考虑一种简单的打怪升级游戏,游戏规则是,给定有 N 个堡垒的地图,堡垒之间有道路相连,每条道路上有一只怪兽把守。怪兽本身有能量,手里的武器有价值。打败怪兽需要的能量等于怪兽本身的能量,而怪兽一旦被打败,武器就归玩家所有 —— 当然缴获的武器价值越高,玩家就越开心。

你的任务有两件:

    1. 帮助玩家确定一个最合算的空降位置,即空降到地图中的某个堡垒,使得玩家从这个空降点出发,到攻下最难攻克(即耗费能量最多)的那个堡垒所需要的能量最小;
    1. 从这个空降点出发,帮助玩家找到攻克任意一个其想要攻克的堡垒的最省能量的路径。如果这种路径不唯一,则选择沿途缴获武器总价值最高的解,题目保证这种解是唯一的。

输入格式

输入第一行给出两个正整数 N (≤1000) 和 M,其中 N 是堡垒总数,M 是怪兽总数。为简单起见,我们将堡垒从 1 到 N 编号。随后 M 行,第 i 行给出了第 i 只怪兽的信息,格式如下:

B1 B2 怪兽能量 武器价值

其中 B1B2 是怪兽把守的道路两端的堡垒编号。题目保证每对堡垒之间只有一只怪兽把守,并且 怪兽能量武器价值 都是不超过 100 的正整数。

再后面是一个正整数 K(≤N)和玩家想要攻克的 K 个目标堡垒的编号。

输出格式

首先在一行中输出玩家空降的堡垒编号 B0。如果有多种可能,则输出编号最小的那个。

随后依次为玩家想要攻克的每个堡垒 B 推荐最省能量的攻克路径,并列出需要耗费的能量值和沿途缴获武器的总价值。注意如果最省力的路径不唯一,则选择沿途缴获武器总价值最高的解。格式为:

B0->途经堡垒1->...->B
总耗费能量 武器总价值

输入样例

6 12
1 2 10 5
2 3 16 20
3 1 4 2
2 4 20 22
4 5 2 2
5 3 12 6
4 6 8 5
6 5 10 5
6 1 20 25
1 5 8 5
2 5 2 1
2 6 8 5
4
2 3 6 5

输出样例

5
5->2
2 1
5->1->3
12 7
5->4->6
10 7
5
0 0

解题思路

首先,需要得到满足最长距离(耗费能量最多)最短(所需要的能量最小)的空降位置(最小能量相同时选择编号小的),即用多源最短路算法——Floyd处理数据,再遍历求出最长距离最短的空降位置。

其次,需要找到空降距离到想要攻克的堡垒的最省能量的路径或消耗能量相同但沿途缴获武器总价值最高的路径,则用单源最短路算法——Dijkstra,找出符合要求的路径,递归输出。

注意:用两种算法时的数组得分别储存分别开,不能用改变后的数组求第二个最短路!

解题代码

#include<bits/stdc++.h>
using namespace std;
#define inf 0x3f3f3f3f
int e[1002][1002],v[1002][1002],pass[1002],d[1002],val[1002];
bool vis[1002];
int minn=inf,ans1;
struct node{int to,p,w;};
vector<node> g[1002];
void print(int x)//递归输出
{
    if(!pass[x]) return ;
    if(pass[x]!=ans1) print(pass[x]);
    cout<<pass[x]<<"->";
}
int main()
{
    int n,m;
    cin>>n>>m;
    memset(e,inf,sizeof(e));
    for(int i=0;i<m;i++){
        int b1,b2,p,w;
        cin>>b1>>b2>>p>>w;
        //分别储存两次求最短路的初始数据
        e[b1][b2]=e[b2][b1]=p;
        g[b1].push_back({b2,p,w});
        g[b2].push_back({b1,p,w});
    }
    //Floyd
    for(int k=1;k<=n;k++)
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)
                if(e[i][j]>e[i][k]+e[k][j])
                    e[i][j]=e[i][k]+e[k][j];
    //遍历求出最长距离最短的空降位置
    for(int i=1;i<=n;i++){
        int maxx=0;
        for(int j=1;j<=n;j++){
            if(i!=j&&maxx<e[i][j])
                maxx=e[i][j];
        }
        if(maxx<minn){
            minn=maxx;
            ans1=i;
        }
    }
    cout<<ans1<<endl;
    //Dijkstra
    memset(d,inf,sizeof(d));
    d[ans1]=0;
    for(int i=0;i<n;i++){
        int z=-1;minn=inf;
        for(int j=1;j<=n;j++){
            if(!vis[j]&&d[j]<minn){
                z=j;
                minn=d[j];
            }
        }
        vis[z]=1;
        for(auto j:g[z]){
            if(!vis[j.to]&&(d[j.to]>d[z]+j.p||(d[j.to]==d[z]+j.p&&val[j.to]<val[z]+j.w))){
                d[j.to]=d[z]+j.p;
                val[j.to]=val[z]+j.w;
                pass[j.to]=z;
            }
        }
    }
    //for(auto i:pass) cout<<i;cout<<endl;//输出检查
    int t;
    cin>>t;
    for(int i=0;i<t;i++){
        int x;
        cin>>x;
        print(x);//输出路径
        cout<<x<<endl;
        cout<<d[x]<<' '<<val[x]<<endl;
    }
    return 0;
}

7-4 疫情防控

疫情尚未结束,严防疫情反复。为了做好疫情防控工作,国内设置了地区风险等级,对于中高风险地区的人员采取限制移动、居家隔离等手段。

为了研究疫情防控对于跨地区交通运输的影响,假设现在有 N 个机场,M 条航线,每天都会新增一个防控地区,一个防控地区会导致一个机场无法正常运作,航线也自然无法正常运行,每天会有 Qi对旅客从 Xi机场前往 Yi机场,请计算有多少对旅客会受到影响无法完成行程。

旅客只要能直达或通过若干次中转,且乘坐的所有航线的出发和到达机场都正常运作,即视作可完成行程。

输入格式

输入第一行是三个整数 N,M,D (1≤N≤5×104, 1≤M≤2×105, 1≤D≤103), 表示机场数、航线数以及新增防控地区的天数。

接下来首先有 M 行,每行给出空格分隔的两个数字 AB,表示编号为 AB 的机场之间有一条航线。航线是双向的,机场编号从 1 到 N

然后是 D 块输入,每块输入内第一行为空格分隔的两个整数 CQ (1≤Q≤ 103),表示新增机场编号为 C 所在的城市为防控地区,今天有 Q 段行程。数据保证新增的城市之前一定不是防控地区。

接下来的 Q 行,每行是空格分隔的两个数字 XY,表示编号为 XY 的机场的一段行程。行程有可能包括之前就已经成为防控地区的城市。

输出格式

对于每天的询问,请在一行中输出在新增了一个防控地区后当天的行程有多少不能成行。

输入样例

5 5 3
1 2
1 3
1 5
2 5
3 4
4 3
1 3
1 4
2 3
5 3
3 4
2 3
3 5
1 3
2 3
2 5
3 4

输出样例

1
2
3

解题思路

通过并查集判断航班是否可以完成行程,即在排除防控地区后两个地区之间是否有路径连通。从而可以先储存输入,再通过按时间倒序遍历的方式判断,每次放出当天防控城市,即在并查集上添加一个点即可。

注意:并查集中必须用路径压缩优化,否则无法通过数据偏大的测试点。

解题代码

#include<bits/stdc++.h>
using namespace std;
int fa[50002],dd[50002],ans[1002];
bool vis[50002];
vector<int> e[50002];
vector<pair<int,int>> fl[50002];
int find(int x)//寻找父节点+路径压缩
{
    return x==fa[x]?x:fa[x]=find(fa[x]);
}
void add(int x,int y)
{
    x=find(x);
    y=find(y);
    if(x!=y) fa[x]=y;
}
int main()
{
    int n,m,d;
    cin>>n>>m>>d;
    //储存输入
    for(int i=0;i<m;i++){
        int a,b;
        cin>>a>>b;
        e[a].push_back(b);
        e[b].push_back(a);
    }
    for(int i=1;i<=d;i++){
        int c,q;
        cin>>c>>q;
        dd[i]=c;//第i天的防控地区为c
        vis[c]=1;//记录c点被防控
        for(int j=0;j<q;j++){
            int x,y;
            cin>>x>>y;
            fl[c].push_back(make_pair(x,y));//记录c点的航班
        }
    }
    //并查集
    for(int i=1;i<=n;i++) fa[i]=i;//初始化
    for(int i=1;i<=n;i++){
        if(!vis[i]){
            for(auto j:e[i])
                if(!vis[j]) add(i,j);//合并
        }
    }
    for(int i=d;i>=1;i--){//按时间倒序
        vector<pair<int,int>> t=fl[dd[i]];
        //通过第i天为止皆未被防控的地区,判断该城市的航班是否可以完成行程
        for(auto j:t){
            int x=find(j.first),y=find(j.second);
            if(x!=y) ans[i]++;
        }
        vis[dd[i]]=0;//放出第i天成为防控地区的城市
        for(auto j:e[dd[i]]){
            if(!vis[j]) add(dd[i],j);//添加该城市至并查集
        }
    }
    //按时间正序输出
    for(int i=1;i<=d;i++) cout<<ans[i]<<endl;
    return 0;
}
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值