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

目录

1.懂的都懂(暴力,map)

2.芬兰木棋(思维,map+vector) 

3.打怪升级 ()

4.疫情防控()


1.懂的都懂(暴力,map)

题目:

现在我们采集了原图的一些特征数据,由 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

思路:

看到数据范围直接暴力,将每4个下标互不相同的数值加以来,利用map来标记;最后遍历每组看是否每个值都可以满足mp[x*4]==true;

代码:

#include <bits/stdc++.h>
#define int long long 
using namespace std;
const int N=1000;
int n,m;
int a[N];

map<int,bool>mp;
void fun()
{
    for(int i=0;i<n;i++){
        for(int j=0;j<n;j++){
            for(int aa=0;aa<n;aa++){
                for(int b=0;b<n;b++)
                {
                    int sum=0;
                    if(i!=j&&i!=aa&&i!=b&&j!=aa&&j!=b&&aa!=b)
                    {
                        sum=a[i]+a[j]+a[aa]+a[b];
                        mp[sum]=true;
                    }
                }
            }
        }
    }
}
void solve()
{
    cin>>n>>m;
    for(int i=0;i<n;i++){
        cin>>a[i];
    }
    fun();
    while(m--)
        
    {
        bool flag=true;
        vector<int>ans;
        int cnt; cin>>cnt;
        for(int i=0;i<cnt;i++){
            int x; cin>>x;
            ans.push_back(x);
        }

        for(int i=0;i<ans.size();i++){
            if(mp[ans[i]*4]==false){
                flag=false;
                break;
            }
        }
        if(flag) cout<<"Yes\n";
        else  cout<<"No\n";
    }
}
signed main()
{
    cin.tie(0); cout.tie(0);
    ios::sync_with_stdio(false);
    int T;
    T=1;
    while(T--){
        solve();
    }
    return 0;
}

2.芬兰木棋(思维,map+vector) 

题目:

芬兰木棋(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时,逐个击打是最优策略,而但棋子的数字恰好是1时,尽量连续击打棋子数字是1的,是最优策略,注意到题目说的是每次击打最近的K个,所以从最开始遍历,只要不是1就跳出;

代码:

#include <bits/stdc++.h>
#define int long long 
using namespace std;
typedef pair<int,int>PII;
const int N=1e5+10;
int n;
struct num
{
    int x,y,p;
}q[N];
map<PII,int>mp;
vector<PII>ans[N];
int gcd(int a,int b)
{
    return b?gcd(b,a%b):a;
}
void solve()
{
    cin>>n;   int sum=0;
    for(int i=0;i<n;i++)
	{
        cin>>q[i].x>>q[i].y>>q[i].p;
        sum+=q[i].p;
    } 
    int id=1;
    for(int i=0;i<n;i++)
	{
        int x=q[i].x;
		int y=q[i].y;
		int mm=gcd(abs(x),abs(y));
		x/=mm;  y/=mm;// 将其约分达到标准向量; 
		if(mp[{x,y}]==0)// 每个PII都是一个数组的编号; 
		{
			mp[{x,y}]=id;
			int dis=q[i].x*q[i].x+q[i].y*q[i].y; 
			ans[id].push_back({dis,q[i].p});
			id++;
		}
		else{
			int u=mp[{x,y}];
			int dis=q[i].x*q[i].x+q[i].y*q[i].y;
			ans[u].push_back({dis,q[i].p});	
		}
    }
    for(int i=1;i<id;i++){
    	sort(ans[i].begin(),ans[i].end());// PII自动按照第一个值从小到大排序; 
	}
	
	int res=n;
	for(int i=1;i<id;i++)
	{
		int cnt=0;
		for(int j=0;j<ans[i].size();j++){
			if(ans[i][j].second==1){
				cnt++;
			}
			else{// 只要出现不是1了,就直接跳出; 
				break;
			}
		}
		if(cnt){
			res-=cnt;
			res++;
		}
	}
    cout<<sum<<" "<<res;
}

signed main()
{
    cin.tie(0); cout.tie(0);
    ios::sync_with_stdio(false);
    int T;
    T=1;
    while(T--){
        solve();
    }
    return 0;
}

3.打怪升级 (Dijkstra()算法)

题目:

很多游戏都有打怪升级的环节,玩家需要打败一系列怪兽去赢取成就和徽章。这里我们考虑一种简单的打怪升级游戏,游戏规则是,给定有 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

思路

第一个问题:
可以直接利用Dijkstra(),将每个点和其他点之间的最短路算出来,然后遍历找出最大距离的,
之后如果有距离比这个小的,就更新下标;

第二个问题:
已经确定了起始点,就只需要考虑起始和终点,找出最短路的同时,如果有两条路径长度一样,则需要考虑哪个路的武器值更大,便走过去,同时记录下路径。

代码:

#include <bits/stdc++.h>
using namespace std;
const int N=1010;
int g[N][N];// 存距离
int v[N][N];// 存武器值
int dis[N];  
bool st[N];
int n,m;
map<int,int>ans;// 路径;
int start; int mmax=1e18;// 落地点,攻克值
map<int,int>price;
void Dijkstra1(int x)
{
    memset(dis,0x3f,sizeof dis);
    memset(st,0,sizeof st);
    dis[x]=0;
    for(int i=0;i<n;i++)
    {
        int t=-1;
        for(int j=1;j<=n;j++){
            if((st[j]==false)&&(t==-1||dis[t]>dis[j])){
                t=j;
            }
        }
        st[t]=true;
        
        for(int j=1;j<=n;j++)
        {
            if(dis[t]+g[t][j]<dis[j]){
                dis[j]=dis[t]+g[t][j];
            }
        }
    }
    
    int u=-1;
    for(int i=1;i<=n;i++){
        u=max(u,dis[i]);
    }
    
    if(u<mmax){
        start=x;
        mmax=u;
    }
}

void Dijkstra2(int u)
{
    memset(dis,0x3f,sizeof dis);
    memset(st,0,sizeof st);
    price.clear();
    dis[start]=0;
    for(int i=0;i<n;i++)
    {
        int t=-1;
        for(int j=1;j<=n;j++){
            if((st[j]==false)&&(t==-1||dis[t]>dis[j])){
                t=j;
            }
        }
        st[t]=true;
        
        for(int j=1;j<=n;j++)
        {
            if(dis[j]>dis[t]+g[t][j])
            {
                dis[j]=dis[t]+g[t][j];
                price[j]=price[t]+v[t][j];
                ans[j]=t;
            }
            else if(dis[j]==dis[t]+g[t][j])
            {
                if(price[j]<price[t]+v[t][j])
                {
                    price[j]=price[t]+v[t][j];
                    ans[j]=t;
                }
                
            }
        }
    }
    
    int node=u;
    vector<int>path;
    while(node)
    {
        path.push_back(node);
        node=ans[node];
    }
    
    for(int i=path.size()-1;i>=0;i--)
    {
        if(i!=path.size()-1) cout<<"->";
        cout<<path[i];
    }
    cout<<"\n";
    cout<<dis[u]<<" "<<price[u]<<"\n";
}
int main()
{
    cin.tie(0); cout.tie(0);
    ios::sync_with_stdio(false);
    memset(g,0x3f,sizeof g);
    cin>>n>>m;
    while(m--)
    {
        int a,b,c,d;
        cin>>a>>b>>c>>d;
        g[a][b]=g[b][a]=c;
        v[a][b]=v[b][a]=d;
    }
    
    for(int i=1;i<=n;i++){
        Dijkstra1(i);
    }
    cout<<start<<"\n";
    int cnt;
    cin>>cnt;
    while(cnt--)
    {
        int x;
        cin>>x;
        Dijkstra2(x);
    }
    
    
    return 0;
    
}



4.疫情防控(思维+并查集)

题目:

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

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

输入格式:

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

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

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

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

输出格式:

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

输入样例:

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;
const int N=5e4+10;
typedef pair<int,int>PII;
int n,m,q;
map<int,bool>s;// 看这个点是否作为疫站; 
int a[N],b[N];
vector<PII>ans[N];
vector<int>e[N];
int p[N];
int res[N];
int find(int x)
{
    if(p[x]!=x) p[x]=find(p[x]);
    return p[x];
}
int main()
{
    cin.tie(0); cout.tie(0);
    ios::sync_with_stdio(false);
    cin>>n>>m>>q;
    while(m--)
    {
        int a,b;
        cin>>a>>b;
        e[a].push_back(b);
        e[b].push_back(a);// 将关联的点连接; 
    }
    for(int i=1;i<=q;i++)
    {
        cin>>a[i]>>b[i];
        s[a[i]]=true;
        for(int j=0;j<b[i];j++){
            int u,v;
            cin>>u>>v;
            ans[i].push_back({u,v});// 记录查询的点的起始和终止; 
        }
    }
    for(int i=1;i<=n;i++) p[i]=i;
    for(int i=1;i<=n;i++)
    {
        if(s[i]==false)// 只要这个点没被标记,就遍历这个点的关联点; 
        {
            for(int j=0;j<e[i].size();j++){
                int ne=e[i][j];
                if(s[ne]==false){
                    int a=find(i);
                    int b=find(ne);
                    if(a!=b) p[b]=a;
                }
            }
        }
    }
    
    for(int i=q;i>=1;i--)// 倒序处理询问; 
    {
        for(int j=0;j<ans[i].size();j++)
        {
            int a=find(ans[i][j].first);
            int b=find(ans[i][j].second);
            if(a!=b) res[i]++;// 起始点和终点不在一个集合中,说明出现问题; 
        }
        
        int r=a[i];// 考虑要将这个点重新变回机场; 上面处理的是被标记的点,而此处处理的是被标记的点; 
        for(int j=0;j<e[r].size();j++)
		{
            if(s[e[r][j]]==false){
                int a=find(e[r][j]);
                int b=find(r);
                if(a!=b) p[a]=b;
            }
        }
        s[a[i]]=false;
        
    }
    for(int i=1;i<=q;i++){
        cout<<res[i]<<"\n";
    }
    
}

  • 13
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值