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

1、懂的都懂

由于本题数据范围很小,所以直接四层for循环枚举预处理所有可能的四个数的和,然后对于新图中每个数的四倍,判断是否出现过即可

C++代码:

#include<iostream>
using namespace std;
const int N=55;
int a[N];
bool st[1040];//260*4
int main(){
    int n,k;
    cin>>n>>k;
    for(int i=1;i<=n;i++)cin>>a[i];
    for(int i=1;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++){
                    int t=a[i]+a[j]+a[k]+a[l];
                    st[t]=true;//标记出现过
                }
    while(k--){
        int m,flag=0;
        cin>>m;
        while(m--){
            int x;
            cin>>x;
            if(!st[x*4])flag=1;//判断x的四倍是否出现过,如果没出现过,则与原图不相似
        }
        if(flag)puts("No");
        else puts("Yes");
    }
    return 0;
}

2、芬兰木棋

- 题目分析:

要求:

  • 如果仅击倒 1 根木棋,则得木棋上的分数。
  • 如果击倒 2 根或以上的木棋,则只得击倒根数的分数。
  • 满足以上两个条件,求获得分数最大,最少需要扔多少次大木棋

所以对于每个方向,从前往后遍历每个木棋:

1、如果木棋的分数大于1,则该木棋一定要单独扔一次大木棋,否则分数一定不是最多

2、如果出现一连串的小木棋的分数等于1,则对这一连串的小木棋扔一次大木棋即可

3、如果只有一个单独的1,则也需要单独扔一次大木棋

由此可知:最大分数一定是所有分数之和

一开始以为只要斜率相同就可以一起处理了,然后遇到全是1的就分成一次投。后来写了发现答案不对劲,斜率相同的点可能贯穿两个象限。如一、三象限,但这两个象限投木棋的方向是不一样的,一个是右上,一个是左下。所以我们需要单独处理四个象限,记得不要忘了坐标轴上的点,也有四种情况↑、↓、←、→一共是八种情况,所以在存储每个斜率的时候还要存储它们的位置(即象限)信息

处理完以上信息之后,对于每种情况,即斜率和象限都相同的的情况

我们需要从前往后枚举每个点,但是何为从前往后呢?

根据题意,是按照到原点的距离进行排序,然后枚举每个点。由于还需判断该点的分数是否等于1,故每种情况的每个点我们要存储两个信息(该点到原点的距离、该点的分数)

typedef pair<double,int> PDI; 

存储结构可以用map<PDI,vector<PDI>> mp;//pair存储斜率和象限,vector存储该种情况的每个点的信息,即距离和分数

C++代码如下:

#include<iostream>
#include<map>
#include<vector>
#include<cmath>
#include<algorithm>
using namespace std;
typedef pair<double,int> PDI;
typedef long long LL;
int n,x,y,p,t;
int cnt,score;//cnt存储最少扔的次数,score存储最多可获得的分数 
double k;//斜率 
map<PDI,vector<PDI>> mp;//PII中存储斜率和象限,vector中存储到原点的距离和分数
int get(int x,int y){//得到象限位置信息
    //没有按照实际的象限编号,因为只要保证八种情况的编号不同即可
    if(x<0&&y<0)return 1;
    else if(x<0&&y==0)return 2;
    else if(x<0&&y>0)return 3;
    else if(x==0&&y<0)return 4;
    else if(x==0&&y>0)return 5;
    else if(x>0&&y<0)return 6;
    else if(x>0&&y==0)return 7;
    else if(x>0&&y>0)return 8;
}
int main(){
    cin>>n;
    for(int i=1;i<=n;i++){
        PDI u,v;//u存储斜率和象限信息,v存储距离和分数信息
        cin>>x>>y>>p;
        score+=p;//分数+p
        if(x!=0)k=1.0*y/x;//求斜率
        else k=0;//x等于0默认k为0,方便存储
        int t=get(x,y);//求位置
        u.first=k,u.second=t;//斜率和位置
        v.first=sqrt((LL)x*x+(LL)y*y),v.second=p;//距离和分数
        mp[u].push_back(v);
    }
    for(auto t:mp){
        vector<PDI> k=t.second;
        //pair默认按照第一个参数进行排序,故会按照距离进行排序
        sort(k.begin(),k.end());
        for(int i=0;i<k.size();i++){
            if(k[i].second>1)cnt++;
            else if(k[i].second==1){
                while(k[i].second==1&&i<k.size())i++;
                cnt++;//一连串的1扔一次
                i--;
            }
        }
    }
    cout<<score<<" "<<cnt<<endl;
    return 0;
}

3、打怪升级

题意分析:

  • 首先要找到空降位置

1、对每个点做一遍最短路,以怪物的能量作为路径长度,故每个点所能到达的最远的地方即为最难攻克的堡垒

2、对于每个点所能到的最远的地方对应的路径,找出这些路径的最小值,该最小值所对应的起点就是空降位置

  • 其次就是处理每个询问

1、用空降位置作为起点再跑一遍最短路

2、依次输出每次询问的最短路径和最大价值

C++代码如下:

#include<iostream>
#include<cstring>
using namespace std;
const int N=1010,INF=0x3f3f3f3f;
int g[N][N],w[N][N];
int dist[N],val[N];//dist存储最短路径,val[i]存的是走到该点最大的价值
bool st[N];
int pre[N];//pre[i]存走到该点的路径
int n,m,k,start,maxv=INF;;
//start存储空降位置,maxv存储每个点攻克最难攻克的堡垒所需的能量的最小值
void dijkstra(int u){//dijkstra算法求最短路模板
    memset(dist,0x3f,sizeof dist);
    memset(st,false,sizeof st);
    dist[u]=0;
    for(int i=0;i<n;i++){
        int t=-1;
        for(int j=1;j<=n;j++)
            if(!st[j]&&(t==-1 ||dist[j]<dist[t]))
                t=j;
        st[t]=true;
        for(int j=1;j<=n;j++){
            if(dist[j]>dist[t]+g[t][j]){
                dist[j]=dist[t]+g[t][j];//更新距离 
                val[j]=val[t]+w[t][j];//更新价值 
                pre[j]=t;//j的上一个点是t 
            }else if(dist[j]==dist[t]+g[t][j]){
                //如果路径能量相同,则选择缴获武器价值最高的方案
                if(val[j]<val[t]+w[t][j]){
                    val[j]=val[t]+w[t][j];//更新价值 
                    pre[j]=t;//j的上一个点是t 
                }
            }
        }
    }
}
int main(){
    memset(g,0x3f,sizeof g);
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++){
        int a,b,c,d;
        scanf("%d%d%d%d",&a,&b,&c,&d);
        g[a][b]=g[b][a]=c;//记录花费
        w[a][b]=d,w[b][a]=d;//记录价值
    }
    for(int i=1;i<=n;i++){//每一个点跑一下最短路,找到最合算的空降位置start
        dijkstra(i);
	    int temp=0;//计算攻下最难攻克的堡垒所需要的能量
	    for(int i=1;i<=n;i++)temp=max(temp,dist[i]);
	    if(temp<maxv)maxv=temp,start=i;//如果能量小于maxv,则更新maxv和start
    }
    printf("%d\n",start);
    memset(val,0,sizeof val);
    memset(pre,0,sizeof pre);
    dijkstra(start);//从出发点出发,走到每个节点
    //处理k个询问
    scanf("%d",&k);
    while(k--){
        int x;
        scanf("%d",&x);
        int u=x,path[N],cnt=0;//path存储路径
        while(u){
            path[cnt++]=u;
            u=pre[u];//u更新为路径中它的前一个节点
        }
        for(int i=cnt-1;i>=0;i--){
            if(i>0)printf("%d->",path[i]);
            else printf("%d",path[i]);
        }
        printf("\n%d %d\n",dist[x],val[x]);
    }
    return 0;
}

4、疫情防控

题意分析:每天给出一个防控地区,若干个当天的行程,判断有多少个行程不能成形

1、暴力做法,可以骗17分

对于每个防控地区,都标记一下,然后dfs搜索从起点到终点是否有不经过防控地区的路线,如果有,则可以出行,如果没有,则不能出行

#include<iostream>
#include<cstring>
using namespace std;
const int N=50010,M=400010;
int h[N],e[M],ne[M],idx;
int state[N];//标记机场是否已经成为防控地区
bool st[N]; 
int n,m,d,flag;
void add(int a,int b){
    e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
void dfs(int sta,int ed){//开始机场和结束机场
    st[sta]=true;
    if(state[sta])return;
    for(int i=h[sta];i!=-1;i=ne[i]){
        int j=e[i];
        if(j==ed&&state[j]==false){
            flag=1;
            return;
        }
        if(!st[j]&&!state[j])dfs(j,ed);
    }
    st[sta]=false;
}
int main(){
    cin>>n>>m>>d;
    memset(h,-1,sizeof h);
    while(m--){
        int a,b;
        cin>>a>>b;
        add(a,b),add(b,a);
    }
    while(d--){
        int c,q,ans=0;
        cin>>c>>q;
        state[c]=true;//c为防控地区
        while(q--){
            memset(st,false,sizeof st);
            int x,y;
            flag=0;
            cin>>x>>y;
            dfs(x,y);
            if(!flag)ans++;
        }
        cout<<ans<<endl;
    }
    return 0;
}

2、正解:并查集

分析:对于每个询问,我们要找是否有一条不经过任何防控地区的路线。换句话说,就是将所有不是防控地区的机场看成若干个连通块,然后判断起点和终点是否连通,如果连通,则可以出行,否则不能

假设一开始所有的机场就是若干个连通块,每次询问的时候就把当天变成防控地区的机场踢出连通块,然后就会发现这样并不好操作

正难则反易,虽然删除连通块中的点很麻烦,但是反过来想,往连通块里加点是很简单的,这可以通过并查集来完成

所以,我们可以离线处理,即先存储所有的询问,然后逆序处理每一天的询问,每次将一个机场加入到连通块中

C++代码:

#include<iostream>
#include<vector>
using namespace std;
const int N=1e5+10;
typedef pair<int,int> PII;
vector<int> v[N];//存储所有边 
vector<PII> query[N];//query[i]存储第i天的所有询问,每个询问的起点和终点 
int p[N];//并查集数组 
int day[N],ans[N];//day[i]:第i天变成防控地区的机场编号,ans[i]:第i天不能出行的路线数 
int n,m,d;
int find(int x){//找祖宗节点 + 路径压缩 
	if(p[x]!=x)p[x]=find(p[x]);
	return p[x];
}
int main(){
	cin>>n>>m>>d;
	for(int i=1;i<=n;i++)p[i]=i;//初始化并查集
	for(int i=1;i<=m;i++){
		int a,b;
		cin>>a>>b;
		//建一条无向边
		v[a].push_back(b);
		v[b].push_back(a);
	}
	//存储询问 
	for(int i=1;i<=d;i++){
		int k; 
		cin>>day[i]>>k;//第i天的防控机场存入到day[i]中 
		p[day[i]]=0;//0标记该机场为防控机场
		while(k--){
			int a,b;
			cin>>a>>b;
			query[i].push_back({a,b});//第i天的所有询问都存入query[i]中
		} 
	} 
	//把所有没有在任何一天变成防控地区的机场合并成若干个连通块 
	for(int i=1;i<=n;i++)
		if(p[i]){
			for(int j=0;j<v[i].size();j++)//遍历所有的相邻节点
				if(p[v[i][j]])
					p[find(v[i][j])]=find(i);
		}
	//逆序处理每一天 
	for(int i=d;i>=1;i--){
		//先处理当天的每个询问 
		for(int j=0;j<query[i].size();j++){
			int fa=find(query[i][j].first);
			int fb=find(query[i][j].second);
			if(fa!=fb||fa==0)ans[i]++;//如果两个节点不连通或者都是防控地区,则该天不能出行的询问加一 
		}
		//然后把该天变为防控地区的机场变成正常机场 
		int k=day[i];
		p[k]=k;
		for(int j=0;j<v[k].size();j++)
			if(p[v[k][j]])p[find(v[k][j])]=k;//合并所有相邻的正常机场 
	}
	for(int i=1;i<=d;i++)cout<<ans[i]<<endl;//输出每一天的答案 
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值