[ioi2018 d2t2] highway - 交互题 - 二分

题目大意:给定n个点m条边的无向简单连通图,图上有s,t两个点,你并不知道是哪两个,每条边的边权可以是a也可以是b(a<b)。每次你可以指定每条边的边权到底是a还是b,交互库会返回此时s到t的最短路长度,要求用不超过50次询问求出s和t。n<=90000,m<=130000。

交互题历来是ioi的一大特色。今年的这道题给人的感觉跟去年的d1t1,d2t2两道交互的风格比较像,当然难度(主要是思维难度)也很高,现场ac的人寥寥无几。

见这种交互题比较多的人容易想到,这类题有一个通用的套路——二分。从50次询问也能看出这题肯定是需要我们二分一个东西。

首先我们可以通过一次全是a的询问,得到s与t之间的最短路长度。

考虑s到t的某条最短路,从中选择一个点u,显然路径上s到u以及u到t的部分也是最短路。这是一个重要的性质,然而如果我们想要利用它,就必须要先找到这个点u。

怎么找呢?对了,二分!

设我们当前二分到mid,我们就将所有与编号为1~mid相连的边全都标记为b,其余标记为a。此时如果s到t的最短路不变,意味着存在一条最短路不经过1~mid中的任何一个点,反之则表示所有最短路都必然会经过1~mid中的一个点。

无论如何,我们都能选出一半的点,使得其中至少有一个点,它被至少一条最短路经过。

如此二分下去,我们就能找到一个点u,它至少在一条最短路上。

然后,我们考虑从u出发开始bfs,打一个bfs序出来。

由于最短路的性质,从s到u的最短路经过的点的bfs序会单调下降,u到t的最短路经过的点的bfs序会单调上升。

我们考虑继续二分:对于当前的mid,我们把所有bfs序>=mid的点,将其出边全部设为b,其余设为a。

如果最短路改变,说明s与t至少有一个点在mid~n,反之两个点都在1~mid。

这样我们可以通过一次二分确定s与t中的一个点,再从这个点开始bfs+二分,就可以确定出另一个点。

这样的总询问次数是1+3logn。然而这就够了吗?

实际上,经过验证,上述算法在最坏情况下的询问次数是52次,就差一点点!

在一系列卡常失败后,我意识到,这个算法在最坏情况下会跑满3logn+1的上限。而通过此题所需要的,可能就是让它变得“不满”一点。

进一步,我们发现,可以在第2、3步的两次二分上再略做文章。

我们把第一步的二分改一下:改成找到最短路上的一条。显然这也是可以用类似的二分求出的。

这样有什么好处呢?考虑由于最短路的性质,设这条边连接u和v,则s一定距离u更近,t一定距离v更近。

那么我们同时从u和v开始bfs,就可以把所有点分成两部分:距离u较近的,距离v较近的。s和t一定在两者中各有一个。

那么我们再分别在两个点集中二分,得到s和t。

这样有什么好处呢?第2、3步的两个logn变成了log|u|+log|v|(这里的u和v是两个点集),由于|u|+|v|=n,因此在这里我们至少可以省下2次询问,于是就成功地把询问次数卡到了上限50次以内!

//以下代码根据ioi提交格式编写

#include<bits/stdc++.h>
#include"highway.h"
#define li long long
using namespace std;
li dis;
int n,m,a,b,ss,tt,q[100010],h,t,u,v;
int bh[100010],n1[100010],n2[100010],nw1,nw2;bool ner[100010],s1,s2;
vector<int> e[100010],p1,p2,qy;
inline bool cx(){
	return ask(qy) == dis;
}
void find_pair(int _n,vector<int> _u,vector<int> _v,int _a,int _b){
	n = _n;m = _u.size();p1 = _u;p2 = _v;
	int l,r,mid,ans,i,j,k,g;
	for(i = 0;i < m;++i){
		e[p1[i]].push_back(i);e[p2[i]].push_back(i);
	}
	qy.resize(m);
	for(i = 0;i < m;++i) qy[i] = 0;
	dis = ask(qy);
	l = 0;r = m - 2;ans = m - 1;
	while(l <= r){
		mid = l + r >> 1;
		for(i = 0;i <= mid;++i) qy[i] = 1;
		for(i = mid + 1;i < m;++i) qy[i] = 0;
		if(cx()) l = mid + 1;
		else ans = mid,r = mid - 1;
	}
	u = p1[ans];v = p2[ans];
	ner[u] = 0;ner[v] = 1;bh[u] = ++nw1;bh[v] = ++nw2;n1[nw1] = u;n2[nw2] = v;
	q[++t] = u;q[++t] = v;
	while(h < t){
		j = q[++h];
		for(i = 0;i < e[j].size();++i){
			k = e[j][i];
			g = p1[k] == j ? p2[k] : p1[k];
			if(bh[g]) continue;
			q[++t] = g;ner[g] = ner[j];
			if(!ner[g]){
				n1[++nw1] = g;bh[g] = nw1;
			}
			else{
				n2[++nw2] = g;bh[g] = nw2;
			}
		}
	}
	l = 2;r = nw1;ans = 1;
	while(l <= r){
		mid = l + r >> 1;
		for(i = 0;i < m;++i) qy[i] = 0;
		for(i = mid;i <= nw1;++i){
			j = n1[i];
			for(k = 0;k < e[j].size();++k){
				g = e[j][k];
				qy[g] = 1;
			}
		}
		if(cx()) r = mid - 1;
		else ans = mid,l = mid + 1;
	}
	ss = n1[ans];
	l = 2;r = nw2;ans = 1;
	while(l <= r){
		mid = l + r >> 1;
		for(i = 0;i < m;++i) qy[i] = 0;
		for(i = mid;i <= nw2;++i){
			j = n2[i];
			for(k = 0;k < e[j].size();++k){
				g = e[j][k];
				qy[g] = 1;
			}
		}
		if(cx()) r = mid - 1;
		else ans = mid,l = mid + 1;
	}
	tt = n2[ans];
	answer(ss,tt);
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值