洛谷P1902 刺杀大使

P1902 刺杀大使 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

 二分答案最明显的特征为:需要求的东西最大值最小或者是最小值最大,对于这一类题目,大多都为二分答案。二分答案和暴力相比,二分答案把线性的时间复杂度变成了logn级别,从而保证整个题目的时间复杂度为nlogn,不会超时。

二分答案的模板:

	int mid;
	int ans=0;
	while(l<=r){
		mid=(l+r)/2;
		if(check(mid)){
			ans=mid;
			l=mid+1;//此处不一定是这样,要根据题目具体进行判断
		}
		else{
			r=mid-1;//此处同理
		}
	}

二分的板子在市面上有很多种,笔者觉得这种可能更容易记忆,相较于while的循环为(l<r)这种,循环中到底是l-1还是r-1,个人觉得太难记忆了。再来说说此处ans的作用,跟普通的二分答案板子不同,市面上大多数二分答案的板子最后都是输出l,r,mid三者之一,那么此处的ans就是保证ans记录的最后一个合法的值,比如一道题目,可能我最后结束的mid不一定合法,倒数第二个mid是合法的,而此处的ans一定会去记录倒数第二个mid,而不会记录第二个mid。这么讲解可能有点抽象,到相应的题目自己debug一下就很快就会明白ans的精妙之处。

二分答案的做法:题目要求我们求什么东西,我们就对什么进行二分,至于其他的条件,都是在check函数中使用。

上述都是代码模板,而二分答案的精髓则在于模板中的check函数,对于check函数,没有什么技巧,只能把题目给出的信息当作对于check的限制条件,然后写出相应的代码。对于本题而言,显而易见,check函数肯定是跟搜索有关的代码,一开始写的dfs代码tle了,于是换成了bfs去搜然后就过了,写完后看题解,发现bfs也是可以ac的,在bfs的过程中好像不用走回头路即可,即无“恢复现场”这一过程。

#include<bits/stdc++.h>

using namespace std;
const int N=5e5+5;
typedef long long ll;
typedef pair<ll,ll> pll;
int mod=1e9+7;
const int maxv=4e6+5;

int n,m;
int a[1005][1005];
int dx[]={1,-1,0,0};
int dy[]={0,0,-1,1};
int f,tar;
int st[1005][1005];
void bfs(int x,int y)
{
	queue<pll> q;
	q.push({x,y});
	while(!q.empty()){
		auto t=q.front();
		q.pop();
		int zx=t.first,zy=t.second;
		if(zx==n) {
			f=1;
			break;
		}
		for(int i=0;i<4;i++){
			int nx=zx+dx[i],ny=zy+dy[i];
			if(st[nx][ny]||nx<1||ny<1||nx>n||ny>m) continue;
			if(a[nx][ny]>tar) continue;
			q.push({nx,ny});
			st[nx][ny]=1;
		}
	}
}
bool check(int x)
{
	f=0;
	tar=x;
	memset(st,0,sizeof st);
	bfs(1,1);
	if(f) return true;
	else return false;
}
void solve()
{	
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++) {
			cin>>a[i][j];
		}
	}
	int l=0,r=1e9;
	int mid;
	int ans=0;
	while(l<=r){
		mid=(l+r)/2;
		if(check(mid)){
			ans=mid;
			r=mid-1;
		}
		else{
			l=mid+1;
		}
	}
	cout<<ans<<endl;
}

int main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	int t;
	t=1;
	//cin>>t;
	while(t--){
		solve();
	}
	system("pause");
	return 0;
}

另外此题还有另外一种解法,就是运用最小生成树的相关知识,即找出一条相关路径,让第一行到最后一行联通,而路径上权值的最大值最小。

对最大值最小这句话进行翻译一下,因为题目给出的是点权,所以要满足权值最大,所以将点权转换为边权的过程中,将边权记为两点间点权较大的,之后就是运用kruskral进行维护,在维护过程中记录最大的边权即可。

#include<bits/stdc++.h>

using namespace std;
const int N=5e5+5;
typedef long long ll;
typedef pair<ll,ll> pll;
int mod=1e9+7;
const int maxv=4e6+5;

ll n,m;
int a[1005][1005];

int get(int x,int y)//得到点的编号
{
	return (x-1)*m+y;
}
int p[maxv];

struct node
{
	int u,v,w;
}e[maxv];

int k;

int find(int x)
{
	if(p[x]!=x) return p[x]=find(p[x]);
	return p[x];
}
void solve()
{	
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			cin>>a[i][j];
			int c=get(i,j);
			p[c]=c;
		}
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			int u=get(i,j);
			int v1=get(i+1,j);
			int v2=get(i,j+1);//因为是无向边,所以只需要连两条
			if(i+1<=n) e[k++]={u,v1,max(a[i][j],a[i+1][j])};
			if(j+1<=m) e[k++]={u,v2,max(a[i][j],a[i][j+1])};
		}
	}
	//cout<<k<<endl;
	sort(e,e+k,[](node a,node b){
		return a.w<b.w;
	});
	int ans=0;
	int st=1,ed=n*m;
	for(int i=0;i<k;i++){
		int u=e[i].u,v=e[i].v,w=e[i].w;
		//cout<<u<<" "<<v<<" "<<w<<endl;
		int fu=find(u),fv=find(v);
		if(fu!=fv){
			p[fu]=fv;
			ans=max(ans,w);
		}
		if(find(st)==find(ed)) break;
	}
	cout<<ans<<endl;
}

int main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	int t;
	t=1;
	//cin>>t;
	while(t--){
		solve();
	}
	system("pause");
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值