# CHAOSEMP - Editorial

Author and Editorialist: Jay Sharma
Testers: Shubham Anand Jain, Aryan Choudhary

MEDIUM

# PREREQUISITES:

Binary Search, Observations, Geometry

# PROBLEM:

You need to guess the coordinates of a hidden integer point lying in the XY plane. You can make two types of queries -

1. Query a single point. In this case, the grader tells you which quadrant or axis the queried point lies in with respect to the hidden point. If both the points are same, the task is solved.
2. Query a rectangle with sides parallel to the axes. In this case, the grader tells you whether the hidden point lies strictly inside, strictly outside or on the perimeter of the queried rectangle. If the hidden point lies on the perimeter, the task is solved.

If your guess was incorrect, the hidden point moves exactly D units in any of the four axis-parallel directions. D is fixed and is known to be either 0 or 1 but the direction is unknown and may vary for different queries.

# SOME HINTS:

Hint 1

Hint 2

Did you get an intuition for Binary Search?

Hint 3

How to handle the shifting of the point in an easy way?

Solution

Decrease the lower bound of the search space and increase the upper bound of the search space by D for both the dimensions.

Hint 4

Can the point be completely guessed using binary search only?

No, the search space can only be reduced upto 4\times 4 using binary search.

Hint 5

Use Type 2 queries to reduce the search space further.

Hint 6

Aren’t some points being over-included in the search space?

Yes, the four corner points cannot be in the search space since the movement of the point is along one direction only and these corner points are unreachable from any of the points in the previous search space.

# QUICK EXPLANATION:

Do a parallel binary search for the point over both the dimensions using the first type of query. To account for the shift in the position, decrease the lower bound and increase the upper bound of the search space for both the dimensions by D. This way, the search space can be reduced to a 4\times 4 grid. Then use a hard-coded sequence of queries (see explanation for details) to get the exact location of the point.

# EXPLANATION:

Let’s start with the first subtask where the truck doesn’t move. This problem can be solved by applying a parallel binary search for the point over both the dimensions simultaneously. A pseudocode for the same is given below -

Parallel Binary Search Implementation
long long INF = 1e+18;
long long lx=-INF,rx=INF;
long long ly=-INF,ry=INF;
while(true)
{
long long mx=(lx+rx)/2;
long long my=(ly+ry)/2;
cout << "1 " << mx << " " << my << endl;
string s;
cin >> s;
if(s=="O")
break;
else if(s=="XP")
{
lx=rx=mx;
ry=my-1;
}
else if(s=="XN")
{
lx=rx=mx;
ly=my+1;
}
else if(s=="PY")
{
ly=ry=my;
rx=mx-1;
}
else if(s=="NY")
{
ly=ry=my;
lx=mx+1;
}
else if(s=="PP")
{
rx=mx-1;
ry=my-1;
}
else if(s=="PN")
{
rx=mx-1;
ly=my+1;
}
else if(s=="NP")
{
lx=mx+1;
ry=my-1;
}
else if(s=="NN")
{
lx=mx+1;
ly=my+1;
}
}

Query Analysis

Let us analyse for X coordinate. Y coordinate can be analysed similarly. Let S_x(i) denote rx-lx+1 after the i-th query. Lets us call this as the search space the for X coordinate. Then the function S_x follows the following recurrence -
S_x(0)=2\times10^{18}+1,
S_x(i+1)=\frac{S_x(i)-1}{2} if S_x(i) is odd and
S_x(i+1)=\frac{S_x(i)}{2} if S_x(i) is even.

It is not hard to see that S_x(i)=0 for i\geq 61. Thus, we require 61 queries in the worst case to guess the point. This is well within the limit of 72 queries.

Now let’s extend this idea of parallel binary search for D=1. Since, we don’t know which direction the truck will go, we need to consider all the 4 cases. This can be done by decreasing both lx and ly by 1 and increasing both rx and ry by 1.

Now, the search space follows the recurrence -
S_x(0)=2\times10^{18}+1,
S_x(i+1)=\frac{S_x(i)+3}{2} if S_x(i) is odd and
S_x(i+1)=\frac{S_x(i)+4}{2} if S_x(i) is even.

The problem here is that if S_x(i)=4 then S_x(i+1)=4 and if S_x(i)=3 then S_x(i+1)=3. So, the search space cannot be reduced beyond 4 or 3 and here comes the role of Type 2 attacks.

I will be describing the optimal solution here which uses 64 queries. For the sub-optimal solutions for Subtasks 2 and 3, refer to the Subtasks section.

First, we will reduce the search space to 5\times 5. Exactly 60 Type 1 queries will be used in the worst case.

Proof

Lemma 1: Any search space of the form 2^k+4 can be reduced to a search space of 5 using exactly k queries.

Proof by induction

Base Case - A search space of 6 (=2^1+4) is reduced to a search space of \frac{6+4}{2}=5 using 1 query.

Induction hypothesis - Let a search space of 2^k+4 be reduced to a search space of 5 using exactly k queries.

Inductive Step - A search space of 2^{k+1}+4 can be reduced to a search space of \frac{2^{k+1}+4+4}{2} = 2^k+4 using 1 query which can be reduced to a search space of 5 using k queries. So, a search space of 2^{k+1}+4 can be reduced to a search space of 5 using exactly k+1 queries.

Lemma 2: Any search space of the form 2^k+3 can be reduced to a search space of 5 using exactly k-1 queries.

Proof by induction

Base Case - A search space of 7 (=2^2+3) is reduced to a search space of \frac{7+3}{2}=5 using 1 query.

Induction hypothesis - Let a search space of 2^k+3 be reduced to a search space of 5 using exactly k-1 queries.

Inductive Step - A search space of 2^{k+1}+3 can be reduced to a search space of \frac{2^{k+1}+3+3}{2} = 2^k+3 using 1 query which can be reduced to a search space of 5 using k-1 queries. So, a search space of 2^{k+1}+3 can be reduced to a search space of 5 using exactly k queries.

Since 2^{60}+4\leq 2\times 10^{18}+1\leq 2^{61}+3, a search space of 2\times 10^{18}+1 can be reduced to a search space of 5 using exactly 60 queries.

After this, the search space will be as follows -

Use a Type 1 attack at the center point as follows -

The worst case is when the truck was at one of the quadrant points. In each case, the new search space will be (Black points are in the search space and Red ones are not) -

Now, use a Type 2 attack as follows -

The worst case is when the truck was either inside or outside the rectangle. In each of these cases, the new search space will be -

Now, use a Type 2 attack as follows -

Once again, the worst case is when the truck was either inside or outside the rectangle. In each of these cases, the new search space will be -

All the points in this search space lie on the perimeter of the rectangle. So, we can finish the task by making a Type 2 attack as follows -

Total number of queries used =60+4=64

First, reduce the search space to 5\times 5 using parallel binary search. As mentioned above, this requires 60 queries.

Then make a Type 2 attack as below -

There are two cases to consider -

Case 1 - IN

The new search space will be -

Make a Type 2 attack as below -

Case 2 - OUT

The new search space will be -

Make a Type 1 attack as below -

In each of these cases, the worst case occurs when the point lies on a 3\times 1 region and the new search space will be -

Now, make a Type 1 attack as below -

In the worst case, the new search space will be -

This can be solved using 2 more queries as shown for the original constraints.

Total number of queries used = 60+3+2=65.

First reduce the search space to 4\times 4 using Parallel Binary Search. This will require 61 queries. The search space will be -

Now, make a Type 2 attack as below -

The case for IN is easy to handle. Let’s see the case for OUT.

The new search space will be -

Now, use a Type 1 attack as follows -

The worst case is when we get XN. In this case, the new search space will be -

This can be solved using 3 queries as shown in the solution for Subtask 3.

Total queries required = 61+2+3=66.

# SOLUTIONS:

Setter's Solution (in C++)
//Problem - Destroy The EMP Chip
//Author - Jay Sharma (jay_1048576)

#include <bits/stdc++.h>
using namespace std;

const long long INF = 1000000000000000000;

bool binarysearch(long long &lx,long long &rx,long long &ly,long long &ry)
{
long long mx=(lx+rx)/2;
long long my=(ly+ry)/2;
cout << "1 " << mx << " " << my << endl;
string s;
cin >> s;
if(s=="O")
return true;
else if(s=="XP")
{
lx=rx=mx;
ry=my-1;
}
else if(s=="XN")
{
lx=rx=mx;
ly=my+1;
}
else if(s=="PY")
{
ly=ry=my;
rx=mx-1;
}
else if(s=="NY")
{
ly=ry=my;
lx=mx+1;
}
else if(s=="PP")
{
rx=mx-1;
ry=my-1;
}
else if(s=="PN")
{
rx=mx-1;
ly=my+1;
}
else if(s=="NP")
{
lx=mx+1;
ry=my-1;
}
else if(s=="NN")
{
lx=mx+1;
ly=my+1;
}
return false;
}

void solve3x3(long long x,long long y)
{
cout << "2 " << x-1 << " " << y-1 << " " << x+1 << " " << y+1 << endl;
string s;
cin >> s;
if(s=="O")
return;
}

void solve3x4(long long lx,long long rx,long long ly,long long ry)
{
cout << "2 " << lx << " " << ly << " " << rx << " " << ry-1 << endl;
string s;
cin >> s;
if(s=="O")
return;
else if(s=="IN")
solve3x3(lx+1,ly+1);
else if(s=="OUT")
solve3x3(lx+1,ry);
}

void solve4x3(long long lx,long long rx,long long ly,long long ry)
{
cout << "2 " << lx << " " << ly << " " << rx-1 << " " << ry << endl;
string s;
cin >> s;
if(s=="O")
return;
else if(s=="IN")
solve3x3(lx+1,ly+1);
else if(s=="OUT")
solve3x3(rx,ly+1);
}

void solve4x4(long long lx,long long rx,long long ly,long long ry)
{
cout << "2 " << lx << " " << ly << " " << rx << " " << ry-1 << endl;
string s;
cin >> s;
if(s=="O")
return;
else if(s=="IN")
solve4x3(lx,rx,ly,ly+2);
else if(s=="OUT")
solve4x3(lx,rx,ly+2,ry+1);
}

void solve5x5(long long lx,long long rx,long long ly,long long ry)
{
rx=lx+4;
ry=ly+4;
long long mx = (lx+rx)/2;
long long my = (ly+ry)/2;
cout << "1 " << mx << " " << my << endl;
string s;
cin >> s;
if(s=="O")
return;
else if(s=="XP")
solve3x4(mx-1,mx+1,ly-1,my);
else if(s=="XN")
solve3x4(mx-1,mx+1,my,ry+1);
else if(s=="PY")
solve4x3(lx-1,mx,my-1,my+1);
else if(s=="NY")
solve4x3(mx,rx+1,my-1,my+1);
else if(s=="PP")
solve4x4(lx-1,mx,ly-1,my);
else if(s=="PN")
solve4x4(lx-1,mx,my,ry+1);
else if(s=="NP")
solve4x4(mx,rx+1,ly-1,my);
else if(s=="NN")
solve4x4(mx,rx+1,my,ry+1);
}

int main()
{
int tt,qu,d;
cin >> tt >> qu >> d;
while(tt--)
{
if(d==0)
{
long long lx=-INF,rx=INF;
long long ly=-INF,ry=INF;
while(true)
{
if(binarysearch(lx,rx,ly,ry))
break;
}
}
else
{
long long lx=-INF,rx=INF;
long long ly=-INF,ry=INF;
bool done=false;
while(rx-lx+1>5||ry-ly+1>5)
{
if(binarysearch(lx,rx,ly,ry))
{
done=true;
break;
}
else
{
lx--;
rx++;
ly--;
ry++;
}
}
if(!done)
{
solve5x5(lx,rx,ly,ry);
}
}
}
return 0;
}

Setter's Solution (in Java)
//Problem - Destroy The EMP Chip
//Author - Jay Sharma (jay_1048576)

import java.util.Scanner;
class CHAOSEMP
{
static Scanner sc = new Scanner(System.in);
static long INF = 1000000000000000000l;
static long lx,rx,ly,ry;
static boolean binarysearch()
{
long mx=(lx+rx)/2;
long my=(ly+ry)/2;
System.out.println("1 "+mx+" "+my);
System.out.flush();
String s = sc.next();
if(s.equals("O"))
return true;
else if(s.equals("XP"))
{
lx=rx=mx;
ry=my-1;
}
else if(s.equals("XN"))
{
lx=rx=mx;
ly=my+1;
}
else if(s.equals("PY"))
{
ly=ry=my;
rx=mx-1;
}
else if(s.equals("NY"))
{
ly=ry=my;
lx=mx+1;
}
else if(s.equals("PP"))
{
rx=mx-1;
ry=my-1;
}
else if(s.equals("PN"))
{
rx=mx-1;
ly=my+1;
}
else if(s.equals("NP"))
{
lx=mx+1;
ry=my-1;
}
else if(s.equals("NN"))
{
lx=mx+1;
ly=my+1;
}
return false;
}
static void solve3x3(long x,long y)
{
System.out.println("2 "+(x-1)+" "+(y-1)+" "+(x+1)+" "+(y+1));
System.out.flush();
String s = sc.next();
if(s.equals("O"))
return;
}
static void solve3x4(long x1,long x2,long y1,long y2)
{
System.out.println("2 "+x1+" "+y1+" "+x2+" "+(y2-1));
System.out.flush();
String s = sc.next();
if(s.equals("O"))
return;
else if(s.equals("IN"))
solve3x3(x1+1,y1+1);
else if(s.equals("OUT"))
solve3x3(x1+1,y2);
}
static void solve4x3(long x1,long x2,long y1,long y2)
{
System.out.println("2 "+x1+" "+y1+" "+(x2-1)+" "+y2);
System.out.flush();
String s = sc.next();
if(s.equals("O"))
return;
else if(s.equals("IN"))
solve3x3(x1+1,y1+1);
else if(s.equals("OUT"))
solve3x3(x2,y1+1);
}
static void solve4x4(long x1,long x2,long y1,long y2)
{
System.out.println("2 "+x1+" "+y1+" "+x2+" "+(y2-1));
System.out.flush();
String s = sc.next();
if(s.equals("O"))
return;
else if(s.equals("IN"))
solve4x3(x1,x2,y1,y1+2);
else if(s.equals("OUT"))
solve4x3(x1,x2,y1+2,y2+1);
}
static void solve5x5(long x1,long x2,long y1,long y2)
{
x2=x1+4;
y2=y1+4;
long mx = (x1+x2)/2;
long my = (y1+y2)/2;
System.out.println("1 "+mx+" "+my);
System.out.flush();
String s = sc.next();
if(s.equals("O"))
return;
else if(s.equals("XP"))
solve3x4(mx-1,mx+1,y1-1,my);
else if(s.equals("XN"))
solve3x4(mx-1,mx+1,my,y2+1);
else if(s.equals("PY"))
solve4x3(x1-1,mx,my-1,my+1);
else if(s.equals("NY"))
solve4x3(mx,x2+1,my-1,my+1);
else if(s.equals("PP"))
solve4x4(x1-1,mx,y1-1,my);
else if(s.equals("PN"))
solve4x4(x1-1,mx,my,y2+1);
else if(s.equals("NP"))
solve4x4(mx,x2+1,y1-1,my);
else if(s.equals("NN"))
solve4x4(mx,x2+1,my,y2+1);
}
public static void main(String[] args)
{
int tt = sc.nextInt();
int qu = sc.nextInt();
int d = sc.nextInt();
while(tt--!=0)
{
if(d==0)
{
lx=-INF;
rx=INF;
ly=-INF;
ry=INF;
while(true)
{
if(binarysearch())
break;
}
}
else
{
lx=-INF;
rx=INF;
ly=-INF;
ry=INF;
boolean done=false;
while(rx-lx+1>5||ry-ly+1>5)
{
if(binarysearch())
{
done=true;
break;
}
else
{
lx--;
rx++;
ly--;
ry++;
}
}
if(!done)
{
solve5x5(lx,rx,ly,ry);
}
}
}
}
}

Tester's Solution
//By TheOneYouWant
#pragma GCC optimize ("-O2")
#include <bits/stdc++.h>
using namespace std;
#define fastio ios_base::sync_with_stdio(0);cin.tie(0)
#define pb push_back
#define mp make_pair
#define fi first
#define se second
#define all(x) x.begin(),x.end()
#define memreset(a) memset(a,0,sizeof(a))
#define testcase(t) int t;cin>>t;while(t--)
#define forstl(i,v) for(auto &i: v)
#define forn(i,e) for(int i=0;i<e;++i)
#define forsn(i,s,e) for(int i=s;i<e;++i)
#define rforn(i,s) for(int i=s;i>=0;--i)
#define rforsn(i,s,e) for(int i=s;i>=e;--i)
#define bitcount(a) __builtin_popcount(a) // set bits (add ll)
#define ln endl
#define getcurrtime() cerr<<"Time = "<<((double)clock()/CLOCKS_PER_SEC)<<endl
#define dbgarr(v,s,e) cerr<<#v<<" = "; forsn(i,s,e) cerr<<v[i]<<", "; cerr<<endl
#define inputfile freopen("input.txt", "r", stdin)
#define outputfile freopen("output.txt", "w", stdout)
#define dbg(args...) { string _s = #args; replace(_s.begin(), _s.end(), ',', ' '); \
stringstream _ss(_s); istream_iterator<string> _it(_ss); err(_it, args); }
void err(istream_iterator<string> it) { cerr<<endl; }
template<typename T, typename... Args>
void err(istream_iterator<string> it, T a, Args... args) {
cerr << *it << " = " << a << "\t"; err(++it, args...);
}
template<typename T1,typename T2>
ostream& operator <<(ostream& c,pair<T1,T2> &v){
c<<"("<<v.fi<<","<<v.se<<")"; return c;
}
template <template <class...> class TT, class ...T>
ostream& operator<<(ostream& out,TT<T...>& c){
out<<"{ ";
forstl(x,c) out<<x<<" ";
out<<"}"; return out;
}
typedef long long ll;
typedef unsigned long long ull;
typedef long double ld;
typedef pair<ll,ll> p64;
typedef pair<int,int> p32;
typedef pair<int,p32> p96;
typedef vector<ll> v64;
typedef vector<int> v32;
typedef vector<v32> vv32;
typedef vector<v64> vv64;
typedef vector<p32> vp32;
typedef vector<p64> vp64;
typedef vector<vp32> vvp32;
typedef map<int,int> m32;
const int LIM=2e5+5,MOD=1e9+7;
const ld EPS = 1e-9;

int xx=0,ff=1;char ch=getchar();
while(ch>'9'||ch<'0'){if(ch=='-')ff=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){xx=(xx<<3)+(xx<<1)+ch-'0';ch=getchar();}
return xx*ff;
}

void query1(ll x, ll y){
cout<<1<<" "<<x<<" "<<y<<ln;
}
void query2(ll xl, ll xr, ll yl, ll yr){
cout<<2<<" "<<xl<<" "<<yl<<" "<<xr<<" "<<yr<<ln;
}

void solve2x(ll xl, ll xr, ll yl, ll yr){
query2(xl, xr, yl, yr);
string ans;
cin>>ans;
assert(ans == "O");
return;
}

void solve33(ll xl, ll xr, ll yl, ll yr){
while(true){
query2(xl, xr, yl, yr);
string ans;
cin>>ans;
if(ans == "O") return;
}
}
void solve34(ll xl, ll xr, ll yl, ll yr){
if(yr-yl > xr-xl){
query2(xl, xr, yl, yr-1);
}
else{
query2(xl, xr-1, yl, yr);
}
string ans;
cin>>ans;
if(ans == "O") return;
if(ans == "IN"){
if(yr-yl > xr-xl){
return solve33(xl, xr, yl, yr-1);
}
else{
return solve33(xl, xr-1, yl, yr);
}
}
if(yr-yl > xr-xl){
return solve33(xl, xr, yr-1, yr+1);
}
else{
return solve33(xr-1, xr+1, yl, yr);
}
}
void solve44(ll xl, ll xr, ll yl, ll yr){
query2(xl, xr, yl, yr-1);
string ans;
cin>>ans;
if(ans == "O") return;
if(ans == "IN"){
return solve34(xl, xr, yl, yr-1);
}
return solve34(xl, xr, yr-1, yr+1);
}

int main(){
// fastio;

int tests, q, d;
cin>>tests>>q>>d;
// assert(q == 72);
// assert(d == 0);
while(tests--){

ll xl = -1e18;
ll xr = 1e18;
ll yl = -1e18;
ll yr = 1e18;

ll m1, m2;
bool done = 0;
while(true){
m1 = (xl + xr)/2;
m2 = (yl + yr)/2;
query1(m1,m2);
string ans;
cin>>ans;
if(ans == "O"){
done = 1;
break;
}
if(ans[0] == 'X'){
xl = m1;
xr = m1;
}
if(ans[0] == 'P'){
xr = m1-1;
}
if(ans[0] == 'N'){
xl = m1+1;
}
if(ans[1] == 'Y'){
yl = m2;
yr = m2;
}
if(ans[1] == 'P'){
yr = m2-1;
}
if(ans[1] == 'N'){
yl = m2+1;
}
if(d == 1){
xl--;
xr++;
yl--;
yr++;
}
if(d == 1 && max(xr-xl, yr-yl) <= 3) break;
}
if(d == 0) continue;
if(done) continue;
// d == 1, square of side atmost 4
if(max(xr-xl, yr-yl) == 2){
solve33(xl, xr, yl, yr);
}
else{
if(min(xr-xl, yr-yl) == 2){
solve34(xl, xr, yl, yr);
}
else solve44(xl, xr, yl, yr);
}
}

return 0;
}


As always, doubts and alternative solutions are welcome.

9 Likes

I generated random testcases and after each query move the point randomly and use assertions to make sure that I always restrict the regions correctly. I forgot to comment the assert statements that I used. But surprisingly I only got WA (so many WA that I stopped counting) and not RTE. Does it have to do something with the interactor? I don’t see any other reason why assert statements didn’t cause RTE.

That’s probably because the judge gave you the WA verdict (because of exceeding number of allowed queries or printing something invalid) before your code can run those asserts. Such kind of issues usually happen in Interactive problems.

For me too , I also included some exit statements when i reached some invalid state to know if there was any bug in my code , but the checker was giving WA instead of Runtime Error , i think since this was a interactive problem , the checker will always give WA even when the program enconters any runtime errors.

The only difference between my AC code and final WA code is that I commented the assertions. So, no way I got WA because of exceeding query limit. Anyway, awesome problem!! Keep up the good work!!

Ok. Your solution may have been correct. But probably because of asserts, your program was terminating before asking all the queries. The grader sees that you have not destroyed the truck and you still have some queries left. So, it will keep waiting for your queries until it gets timed out. Since it didn’t receive the correct answer, it gives you a WA. There is no way the grader knows whether your program terminated because of TLE or RTE.

1 Like

Couldn’t solve during contest. Was waiting for it for long! Amazing constructive problem.

1 Like

Very nice problem! Tried a lot on this one but was able to clear 1st sub-task only!!But really enjoyed solving it during contest.

1 Like

Great question… I could only partially sovled it, after seeing the solution I realize that I missed the last 4x4 part…