P1058 单舟见京杭 官方题解
【题意分析】
-
用 与 表示两处重复景观。一次选择记为 ,其中 。
-
本题中称 同时选中重复景观,当且仅当 且 。后文不再使用含义容易混淆的“命中”。
-
是重复景观被重复计算的一份美观度,因此 。
-
因为所有美观度均为正数,所以 当且仅当 同时选中两处重复景观。
-
这给出一个只依赖返回值的判定函数,之后每次试探都只需判断这个不等式。
【部分分:测试点 111】
- 当 时,可以枚举一位旅客在每段水道选择的方向。
- 比较
Travel的返回值与自行计算的 ,即可判断当前选择是否同时选中两处重复景观。 - 枚举全部 种选择即可确定答案。
【部分分:测试点 222】
- 特殊性质 A 保证两处重复景观位于同一侧。
- 先安排一位旅客全部向左看,再安排一位旅客全部向右看。其中恰有一种选择会同时选中两处重复景观。
- 固定这份选择。接下来翻转一批位置,便能判断这批位置是否与两处重复景观相交。
【部分分:测试点 333】
-
若两处景观方向不同,则在能够区分 的二进制位上,原选择与其补集必有一个同时满足 。
-
对每个二进制位,令第 段水道的方向等于编号 的这一位,再询问一次其补集。
-
对任意两个不同编号,总有一个二进制位能够区分它们。配合原选择与补集,总能得到一份同时选中两处重复景观的选择。
-
至多使用 位旅客就能得到这样一份选择。
【部分分:测试点 444】
-
设当前选择 已经同时选中两处重复景观。把候选位置的一半记为 ,同时翻转所有 。
-
翻转后的选择不再同时选中两处重复景观,当且仅当 。此时保留 ,否则保留候选集合的另一半。
-
不断折半即可找到第一处重复景观。将它从候选集合中删除后重复同样的过程,即可找到第二处。
-
每次试探后恢复 。最后用
Answer给出两处位置以及 。 -
找到第一处后,从候选集合中删去它。此时每次翻转集合都不含第一处,于是同一个判定会精确告诉我们集合中是否含第二处。
【正解】
- 先用全左、全右、各二进制位及其补集找到一份同时选中两处重复景观的选择。
- 再用两次折半定位分别找到两个位置。每次试探后都恢复选择,已经得到的信息始终有效。
- 总调用次数不超过
在 时不超过 ,可以取得满分。
【参考代码】
#include "canal.h"#include <vector>using namespace std; void Init(int c,int T){return;} static long long Base(const vector<int> &Choice,const vector<int> &w,const vector<int> &v){ long long Ret=0; for(int i=1;i<(int)Choice.size();i++) Ret+=Choice[i]?v[i]:w[i]; return Ret;} static bool Hit(vector<int> &Choice,const vector<int> &w,const vector<int> &v){ return Travel(Choice)<Base(Choice,w,v);} void Locate(int c,int n,vector<int> w,vector<int> v){ vector<int> Choice(n+1,0); bool Found=Hit(Choice,w,v); if(!Found){ for(int i=1;i<=n;i++) Choice[i]=1; Found=Hit(Choice,w,v); } for(int Bit=0;!Found&&(1<<Bit)<=n;Bit++){ for(int i=1;i<=n;i++) Choice[i]=(i>>Bit)&1; Found=Hit(Choice,w,v); if(!Found){ for(int i=1;i<=n;i++) Choice[i]^=1; Found=Hit(Choice,w,v); } } vector<int> Candidate; for(int i=1;i<=n;i++) Candidate.push_back(i); while(Candidate.size()>1){ int Mid=Candidate.size()/2; for(int i=0;i<Mid;i++) Choice[Candidate[i]]^=1; bool Has=!Hit(Choice,w,v); for(int i=0;i<Mid;i++) Choice[Candidate[i]]^=1; if(Has) Candidate.erase(Candidate.begin()+Mid,Candidate.end()); else Candidate.erase(Candidate.begin(),Candidate.begin()+Mid); } int x=Candidate[0],a=Choice[x]; Candidate.clear(); for(int i=1;i<=n;i++) if(i!=x) Candidate.push_back(i); while(Candidate.size()>1){ int Mid=Candidate.size()/2; for(int i=0;i<Mid;i++) Choice[Candidate[i]]^=1; bool Has=!Hit(Choice,w,v); for(int i=0;i<Mid;i++) Choice[Candidate[i]]^=1; if(Has) Candidate.erase(Candidate.begin()+Mid,Candidate.end()); else Candidate.erase(Candidate.begin(),Candidate.begin()+Mid); } int y=Candidate[0],b=Choice[y]; Answer(x,a,y,b);}