Codeforces Round #719 (Div. 3)(EFG)

本文探讨了编程竞赛中涉及的算法问题,如羊群排列和寻找第K个零。对于E.ArrangingTheSheep问题,解决方案是计算羊群移动到中间位置所需的总步数。F1和F2两题都是关于区间零计数的,通过二分查找策略高效求解。G.ToGoOrNotToGo?问题则涉及最短路径计算,考虑是否使用传送门。
摘要由CSDN通过智能技术生成

E. Arranging The Sheep

最终答案就是移动到中间那只羊,羊是偶数的话就靠近中间任取一只就行。
∗ . ∗ . . . ∗ . ∗ ∗ *.*...*.** .....
羊都往第三只那里移动,即下标为7的那只羊,可以发现3会移动3步,1会移动4步,9移动1,10移动1.一共移动9步。且每只羊移动的步数就是与中间羊之间的.的数量

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10;
string s;
int main()
{
	int t;
	cin>>t;
	while(t--)
	{
		int n,cur=0;
		cin>>n;
		cin>>s;
		vector<int>a;
		for(int i=0;i<n;i++)
		{
			if(s[i]=='.')++cur;
			else a.push_back(cur);
		}
		if(a.empty())
		{
			puts("0");
			continue;
		}
		int m=a[a.size()/2];
		long long ans=0;
		for(auto i:a)ans+=abs(m-i);
		cout<<ans<<endl;
		a.clear();
	}
	return 0;
}

F1 Guess the K-th Zero (Easy version)

询问给出的和实际上就是告诉你区间 l-r 之间1的个数
所以用区间长度-询问就是区间0的个数,然后就可以用二分了
当 1~mid的0的个数>=k 的时候 r=mid, l=mid+1
(l=mid+1 因为个数<k说明1~mid的0不够多,要把mid往右边挪动)
这样就可以让 l 不断逼近答案。

最终操作次数就是log的
代码中的g数组是F2分组用的,不用管

#include<bits/stdc++.h>
using namespace std;
const int N=2e4+10;
int g[N],cnt=0,n,k;
int ask(int l,int r)
{
	cout<<'?'<<' '<<l<<' '<<r<<endl;
	int x;
	cin>>x;
	return x;
}
void print(int x)
{
	cout<<'!'<<' '<<x<<endl;
	int id=(x+10-1)/10;
	for(int i=id;i<=cnt;i++)g[i]--;
}
bool check(int mid)
{
	return mid-ask(1,mid)>=k;
}
int main()
{
	int t,f=0;
	cin>>n>>t;
	while(t--)
	{
		scanf("%d",&k);
		int l=1,r=n;
		while(l<r)
		{
			int mid=l+r>>1;
			if(check(mid))r=mid;
			else l=mid+1;
		}
		print(l);
	}
	return 0;
}

F2. Guess the K-th Zero (Hard version)

1.先将n按长度10分组。即[1-10][11-20][21-30]…
然后右端点不能超过n。比如最后一组是[21-28]
把每组依次编号1,2,3,。。。。
分出2e4组

2.对每一组求前缀0的个数,用掉2e4 次query,
query(1,每组的右端点)
这样我们就可以扫一遍,快速确认要到哪个分组里面进行二分
此时还剩下4e4次query

题目一共会有1e4次询问
确认到哪个分组里进行二分要log(10)次,大约是4次
所以是4e4次query,刚刚好。

交互题一般思路:二分,分组,分块,二进制。
注意右端点不能超过n

#include<bits/stdc++.h>
using namespace std;
const int N=2e4+10;
int g[N],cnt=0,n,k;
int ask(int l,int r)
{
	cout<<'?'<<' '<<l<<' '<<r<<endl;
	int x;
	cin>>x;
	return x;
}
void print(int x)
{
	cout<<'!'<<' '<<x<<endl;
	int id=(x+10-1)/10;
	for(int i=id;i<=cnt;i++)g[i]--;
}
bool check(int mid)
{
	return mid-ask(1,mid)>=k;
}
int main()
{
	int t,f=0;
	cin>>n>>t;
	while(t--)
	{
		scanf("%d",&k);
		if(!f)
		{
			for(int i=1;i<=n;i+=10)
			{
				int r=min(i+10-1,n);
				g[++cnt]=r-ask(1,r);
			}
			f=1;
		}
		int l=0,r=1;
		while(g[r]<k)r++;
		r=10*r,l=r-9;
		r=min(r,n);
		while(l<r)
		{
			int mid=l+r>>1;
			if(check(mid))r=mid;
			else l=mid+1;
		}
		print(l);
	}
	return 0;
}

G. To Go Or Not To Go?

传送门最多使用一次就行。

设ms是从起点到传送门的最短花费(包括传送门费用和路程花费),从起点bfs一遍,到每个点的最短距离,同时如果该点是传送门的话,更新
m s = m i n ( m s , d i s [ x ] [ y ] + a [ x ] [ y ] ) ms=min(ms,dis[x][y]+a[x][y]) ms=min(ms,dis[x][y]+a[x][y])

然后从终点弄个me,从终点做同样的操作

如果ms和me都不是无穷大,则ans=ms+me,还有一种可能就是不走传送门,然后两种情况取个min就是最终答案。

#include<bits/stdc++.h>
using namespace std;
const int N=2e3+10;
typedef long long ll;
#define pii pair<int,int>
ll n,m,w,a[N][N],dis[N][N],dx[]= {0,0,1,-1},dy[]= {1,-1,0,0},ms,me;
queue<pii>q;
void bfs1()
{
	memset(dis,-1,sizeof dis);
	ms=1e18;
	q.push({1,1});
	dis[1][1]=0;
	while(!q.empty())
	{
		auto t=q.front();
		q.pop();
		int x=t.first,y=t.second;
		if(a[x][y]>0)ms=min(ms,dis[x][y]+a[x][y]);
		for(int i=0; i<4; i++)
		{
			int nx=x+dx[i],ny=y+dy[i];
			if(nx<1||nx>n||ny<1||ny>m||a[nx][ny]==-1||dis[nx][ny]!=-1)continue;
			dis[nx][ny]=dis[x][y]+w;
			q.push({nx,ny});
		}
	}
}
void bfs2()
{
	memset(dis,-1,sizeof dis);
	me=1e18;
	while(!q.empty())q.pop();
	q.push({n,m});
	dis[n][m]=0;
	while(!q.empty())
	{
		auto t=q.front();
		q.pop();
		int x=t.first,y=t.second;
		if(a[x][y]>0)me=min(me,dis[x][y]+a[x][y]);
		for(int i=0; i<4; i++)
		{
			int nx=x+dx[i],ny=y+dy[i];
			if(nx<1||nx>n||ny<1||ny>m||a[nx][ny]==-1||dis[nx][ny]!=-1)continue;
			dis[nx][ny]=dis[x][y]+w;
			q.push({nx,ny});
		}
	}
}
int main()
{
	cin>>n>>m>>w;
	for(int i=1; i<=n; i++)
		for(int j=1; j<=m; j++)scanf("%lld",&a[i][j]);
	bfs1();
	bfs2();
	ll ans=dis[1][1];
	if(ms!=1e18&&me!=1e18)
	{
		if(ans!=-1)ans=min(ans,ms+me);
		else ans=ms+me;
	}
	cout<<ans;
	return 0;
}
  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值