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

7-1 懂的都懂 (20 分)

题意:

给出n个数a1,a2,a3…an。(n≤50)
给出m个数,判断这个数是否为 从n个数中抽4个,这4个数的平均数?

思路:

n很小,m很大。如果依次判断这m个数的话,每次遍历找n个数中的4个,复杂度很高。
所以可以将这n个数中任意4个数的所有平均数map标记下来,然后O(1)判断这m个数是否出现过就行了。

只要标记4个数的和,判断4*x是否出现过。

Code:

const int N = 200010, mod = 1e9+7;
int T, n, m, a[N];

int main(){
	cin>>n>>m;
	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++){
					mp[a[i]+a[j]+a[k]+a[l]]=1;
				}
			}
		}
	}
	
	for(int i=1;i<=m;i++)
	{
		int k,flag=0;
		cin>>k;
		for(int j=1;j<=k;j++)
		{
			int x;cin>>x;
			if(!mp[4*x]) flag=1;
		}
		if(flag) cout<<"No\n";
		else cout<<"Yes\n";
	}
	
	return 0;
}

7-2 芬兰木棋 (25 分)

题意:

给出n个二维坐标,每个坐标有一个权值。
一个人可以瞄准一个角度,可以选择下面两种操作:

  • 打中距离最近的一个点,获得这个点的所有权值wi (1 ≤ w ≤ 1000)。
  • 打中距离最近的 k 个点,获得 k 个权值。

问最多能够得到多少权值,在这种情况下,最少操作多少次?

思路:

最多能够得到的权值肯定是所有点的权值之和,那么能够得到所有点的权值最少的操作数为多少呢?
为了能够一次操作打倒多个目标,并且没有权值损失,那么这个操作打倒的只能为权值都为1的目标点。

如何能按距离从小到大遍历一个角度上的所有点呢?如何表示角度呢?

通常都是用斜率来表示,y/x,但是还需要将第一象限和第三象限、第二象限和第四象限分别标记。还要判断 x 为0的情况。

题解介绍了另一个表示角度(斜率)的方法~
将分子分母化为最简分数,然后将分子分母分别存到pair中,用map映射
为了区分第一象限和第二象限,分子 x,分母 y 要除以 g c d ( ∣ x ∣ , ∣ y ∣ ) gcd(|x|,|y|) gcd(x,y)加绝对值

但是要把这个角度上的所有点存下来,那么这个 map 就要映射为一个数组,我们可以将 vector 嵌入为 map 的第二元素。这个点要存下两个元素,距离和权值。(因为后面要按距离将所有的坐标排序)所以可以将 pair 嵌入到 vector 中

所以 map 的形式就为:
map< pair<int,int>, vector<pair<int,int>> > mp;

Code:

map <PII,vector<PII>> mp;
const int N = 200010, mod = 1e9+7;
int T, n, m, a[N];

bool cmp(PII a,PII b){
	return a.first<b.first;
}

int main(){
	cin>>n;
	ll sum=0;
	for(int i=1;i<=n;i++){
		int x,y,z;cin>>x>>y>>z;
		int t=__gcd(abs(x),abs(y));
		mp[{x/t,y/t}].push_back({(ll)x*x+(ll)y*y,z});
		sum+=z;
	}
	
	ll cnt=0;
	for(auto i=mp.begin();i!=mp.end();i++) //遍历所有的角度(斜率)
	{
		auto v=i->second;
		sort(v.begin(),v.end()); //将这个角度上的点按距离排序
		
		for(int j=0;j<v.size();j++)
		{
			if(j&&v[j].second==v[j-1].second&&v[j].second==1) continue;
			cnt++;
		}
	}
	cout<<sum<<" "<<cnt;
	
	return 0;
}

技巧性很强的一道题,让我认识到了map还能映射为vector!


7-3 打怪升级 (25 分)

思路:

floyd跑最短路,暴力找出起点。
单源最短路:dijkstra跑最短路,在满足到一个点距离最短的前提下,还要保证价值就总和最大。记录路径。

更新相邻点时:

  • 相邻点最短距离可更新,那么更新此点距离,更新价值,放入队列。
  • 相邻点距离为最短,但是价值可更新,那么更新价值,不放入队列。

Code:

const int N = 1010,M = 2000010;
int T, n, m, a[N];
int e[M],ne[M],h[N],w[M],val[M],idx;
int dist[N],f[N],sum[N];
int pre[N];
int d[N][N];

void add(int x,int y,int tw,int tval){
	e[idx]=y,w[idx]=tw,val[idx]=tval,ne[idx]=h[x],h[x]=idx++;
}

int dij(int st)
{
	mem(dist,0x3f);
	
	for(int i=1;i<=n;i++) pre[i]=i;
	
	priority_queue<PII,vector<PII>,greater<PII> > que;
	que.push({0,st});
	dist[st]=0;
	
	while(que.size())
	{
		int x=que.top().second;
		que.pop();
		if(f[x]) continue;
		f[x]=1;
		
		for(int i=h[x];i!=-1;i=ne[i])
		{
			int tx=e[i];
			if(dist[tx]>dist[x]+w[i]){
				dist[tx]=dist[x]+w[i];
				sum[tx]=sum[x]+val[i];
				pre[tx]=x;
				que.push({dist[tx],tx});
			}
			if(dist[tx]==dist[x]+w[i]){
				if(sum[tx]<sum[x]+val[i]){
					sum[tx]=sum[x]+val[i];
					pre[tx]=x;
				}
			}
		}
	}
	int maxa=-1;
	for(int i=1;i<=n;i++){
		if(dist[i]>maxa) maxa=dist[i]; 
	}
	return maxa;
}

void pree()
{
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
			if(i!=j) d[i][j]=0x3f3f3f3f;
}

void floyd()
{
	for(int k=1;k<=n;k++){
		for(int i=1;i<=n;i++){
			for(int j=1;j<=n;j++){
				if(d[i][j]>d[i][k]+d[k][j]) d[i][j]=d[i][k]+d[k][j];
			}
		}
	}
}

int main(){
	Ios;
	cin>>n>>m;
	
	mem(h,-1);
	
	pree();
	
	while(m--)
	{
		int x,y,w,val;
		cin>>x>>y>>w>>val;
		add(x,y,w,val);
		add(y,x,w,val);
		d[x][y]=d[y][x]=w;
	}
	
	int k;cin>>k;
	for(int i=1;i<=k;i++) cin>>a[i];
	
	floyd();
	
	int st,mina=1e9;
	for(int i=1;i<=n;i++){
		int t=-1;
		for(int j=1;j<=n;j++){
			if(d[i][j]>t) t=d[i][j];
		}
		if(t<mina) mina=t,st=i;
	}
	cout<<st<<endl;
	
	dij(st);
	
	for(int i=1;i<=k;i++){
		int x=a[i];
		cout<<st;
		while(x!=pre[x]){
			stk.push(x);
			x=pre[x];
		}
		while(stk.size()) cout<<"->"<<stk.top(),stk.pop();
		cout<<endl;
		
		cout<<dist[a[i]]<<" "<<sum[a[i]]<<endl;
	}
	
	return 0;
}

7-4 疫情防控 (30 分)

倒序并查集。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值