ROTATPOL - Editorial

PROBLEM LINK:

Practice
Div-2 Contest
Div-1 Contest

Author: tautsjasiunsas
Tester: alei_adm
Editorialist: tautsjasiunsas

DIFFICULTY:

MEDIUM-HARD?

PREREQUISITES:

Geometry, 2-pointers

PROBLEM:

Given a polyline P, find a direction v such that all indices i with v \cdot (P_{i+1}-P_i) > 0 form a subsegment and none are equal to 0.

QUICK EXPLANATION:

First, create an array A_i = P_i- P_{i+1}.
After, make a vector S storing all A_i, -A_i, (0,1), (0,-1), (1,0), (-1,0).
Sort S by polar angle.
Iterate over S, let (x, y) = S_i + S_{i+1}, check (y, -x) for an answer.
This gives O(n^2) solution. To make it faster use 2-pointers.

EXPLANATION:

If we look at the formula of dot product: v \cdot a = |v|\cdot|a|\cdot cos(\alpha).
Let’s imagine we sweep through all possible v (with real coordinates, v = (\cos(\theta), \sin(\theta))).
It is easy to see that the sign of v \cdot (P_{i+1}-P_i) changes then it is 0.
It can only be 0 iff angle(a)= \theta\pm\pi/2. We can create those v by rotating P_{i+1}-P_i 90 degrees clockwise and counterclockwise.
Let’s add all those vectors to a candidate list S.
Sort S, so that \theta is increasing (and remove duplicates).
If we iterate over S and check a vector with \theta between S_i and S_{i+1} we would find an answer. (S_i can never be an answer)
If we add (0,1), (0, -1), (1,0), (-1,0) to S, an angle between S_i and S_{i+1} is at most 90. This means that S_i+S_{i+1} angle will always be between S_i and S_{i+1} (and have integer coordinates), checking all those vectors would give O(n^2) solution.
Let’s make our solution faster.
After we created all the candidate answers in O(n\log(n)), we should sort array A_i by polar angle. While we iterate over candidate answers, it is easy to maintain 2-pointers l,r, such that all the indices with positive dot product are in the interval [l, r]. To check if an answer is valid we should check if the original indices form a continuous subsegment max - min = r - l.

SOLUTIONS:

Setter's Solution

/*input
1
7
500000000 0
0 500000000
0 0
0 -500000000
-500000000 0
-500000000 -500000000
500000000 500000000
*/
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
//point struct and basic operations
struct point
{
ll x, y;
point() {x = 0; y = 0;}
point(ll x, ll y): x(x), y(y) {}
int quarter()const
{
if (x >= 0 && y >= 0)
return 0;
if (x <= 0 && y >= 0)
return 1;
if (x <= 0 && y <= 0)
return 2;
if (x >= 0 && y <= 0)
return 3;
assert(false);
return -1;
}
};
point operator-(const point &a, const point &b)
{
return point(a.x - b.x, a.y - b.y);
}
point operator+(const point &a, const point &b)
{
return point(a.x + b.x, a.y + b.y);
}
ll cross(const point &a, const point &b)
{
return a.x * b.y - a.y * b.x;
}
ll dot(const point &a, const point &b)
{
return a.x * b.x + a.y * b.y;
}
//comparator for sorting A and X
bool operator<(pair<point, int> c, pair<point, int> d) {
if (c.first.quarter() != d.first.quarter())
return c.first.quarter() < d.first.quarter();
ll cr = cross(c.first, d.first);
if (cr != 0)
return cr > 0;
else
return c.second < d.second;
}
int main()
{
ios_base::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
int t;
cin >> t;
assert(1 <= t && t <= 5);
while (t–)
{
int n;
cin >> n;
assert(n >= 3 && n <= 100000);
vectorP(n + 1);
for (int i = 0; i < n; i++)
{
cin >> P[i].x >> P[i].y;
assert(abs(P[i].x) <= 500000000);
assert(abs(P[i].y) <= 500000000);
}
P[n] = P[0];

	//storing all points in a sorted vector
	vector<pair<point, int>>A;
	for (int i = 0; i < n; i++)
	{
		A.push_back({P[i] - P[i + 1], i});
		A.push_back({P[i + 1] - P[i], -1});
	}
	//adding some more vectors so A[i]+A[i+1] would work properly
	for (int x = -1; x <= 1; x++)
	{
		for (int y = -1; y <= 1; y++)
		{
			if (x != 0 || y != 0)
				A.push_back({point(x, y), -1});
		}
	}
	sort(A.begin(), A.end());
	//X[side][quarter]
	//Y[side]
	set<pair<point, int>>X[3][4];
	set<int>Y[3];
	for (auto i : A)
	{
		if (i.second != -1)
		{
			X[0][i.first.quarter()].insert(i);
			Y[0].insert(i.second);
		}
	}
	A.push_back(A[0]);
	for (int i = 0; i + 1 < (int)A.size(); i++)
	{
		point v = A[i].first + A[i + 1].first;
		//0 ->  >0
		//1 ->  <0
		//2 ->  =0
		auto s = [&](pair<point, int>a)
		{
			ll val = cross(v, a.first);
			if (val > 0)
				return 0;
			if (val < 0)
				return 1;
			return 2;
		};
		for (int c = 0; c < 3; c++)
		{
			for (int k = 0; k < 4; k++)
			{
				while (!X[c][k].empty())
				{
					auto it = X[c][k].begin();
					if (s(*it) != c)
					{
						Y[s(*it)].insert(it->second);
						Y[c].erase(it->second);
						X[s(*it)][k].insert(*it);
						X[c][k].erase(it);
						continue;
					}
					it = --X[c][k].end();
					if (s(*it) != c)
					{
						Y[s(*it)].insert(it->second);
						Y[c].erase(it->second);
						X[s(*it)][k].insert(*it);
						X[c][k].erase(it);
						continue;
					}
					break;
				}
			}
		}
		bool ok = false;
		if (Y[2].empty())
		{
			for (int t : {0, 1})
			{
				assert(!Y[t].empty());
				int sz = Y[t].size();
				int diff = (*(--Y[t].end())) - (*Y[t].begin()) + 1;
				if (diff == sz)
				{
					cout << v.y << " " << -v.x << "\n";
					goto GG;
				}
			}
		}
	}
	cout << "0 0\n";

GG:;
}
}

Tester's Solution

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

#define int long long
const int _ = 2e5 + 7; int T , N;
struct comp{
int x , y , id; comp(int _x = 0 , int _y = 0) : x(x) , y(y){}
friend comp operator +(comp p , comp q){return comp(p.x + q.x , p.y + q.y);}
friend comp operator -(comp p , comp q){return comp(p.x - q.x , p.y - q.y);}
friend long long operator %(comp p , comp q){return 1ll * p.x * q.y - 1ll * p.y * q.x;}
friend long long operator *(comp p , comp q){return 1ll * p.x * q.x + 1ll * p.y * q.y;}
friend bool operator <(comp p , comp q){
bool fp = p.y < 0 || (p.y == 0 && p.x < 0) , fq = q.y < 0 || (q.y == 0 && q.x < 0);
return fp == fq ? p % q > 0 : fp > fq;
//return atan2(p.y , p.x) < atan2(q.y , q.x);
}
}P[
] , dir[
];

bool check(vector < comp > now , comp cur){
for(auto t : now) if(cur * t <= 0) return 0;
return 1;
}

signed main(){
for(cin >> T ; T ; --T){
cin >> N; for(int i = 1 ; i <= N ; ++i) cin >> P[i].x >> P[i].y;
P[N + 1] = P[1]; for(int i = 1 ; i <= N ; ++i){dir[i] = P[i + 1] - P[i]; dir[i].id = i;}
sort(dir + 1 , dir + N + 1); dir[0] = dir[N]; for(int i = 1 ; i <= N ; ++i) dir[i + N] = dir[i];
int pl = 1 , pr = 1; comp ans; set < int >in;
while(pr < 2 * N){
if(in.size() && *–in.end() - *in.begin() == in.size() - 1){
vector < comp > now; now.push_back(dir[pl]); now.push_back(dir[pr - 1]);
now.push_back(comp() - dir[pr]); now.push_back(comp() - dir[pl - 1]);

			for(int i = 0 ; i < 4 ; ++i)
				for(int j = 0 ; j < 4 ; ++j)
					if(check(now , i == j ? now[j] : comp(now[j].y - now[i].y , now[i].x - now[j].x)))
						ans = i == j ? now[j] : comp(now[j].y - now[i].y , now[i].x - now[j].x);
		}
		if(dir[pr] % dir[pl] > 0 || (dir[pr] % dir[pl] == 0 && dir[pr] * dir[pl] < 0)) in.erase(dir[pl++].id);
		else in.insert(dir[pr++].id);
	}
	if(ans.x || ans.y){
		in.clear();
		for(int i = 1 ; i <= N ; ++i){
			assert(dir[i] * ans != 0);
			if(dir[i] * ans > 0) in.insert(dir[i].id);
		}
		assert(*--in.end() - *in.begin() == in.size() - 1);
	}
	assert(abs(ans.x) <= 2e9 && abs(ans.y) <= 2e9);
	cout << ans.x << ' ' << ans.y << endl;
}
return 0;

}

3 Likes

Is this editoral supposed to be out???

can you elaborate ?whose value is zero?

Given a vector \vec{u}, its dot product with a vector \vec{v} will be 0 iff \vec{u} \perp \vec{v}. All the vectors \vec{i} that are on the same side of the perpendicular as \vec{u} will make a positive dot product with it, and all the vectors \vec{j} that are on the opposite side of the perpendicular than \vec{u} will make a negative dot product with it.

Now for every position vector \vec{u_i} = (x_i,\ y_i) centered at the origin, we can find two vectors \vec{v_{i1}} = (y_i,\ -x_i) and \vec{v_{i2}} = (-y_i,\ x_i) that will be on the opposite ends of the perpendicular \vec{v_i}. When you sort these in anticlockwise order with respect to the origin, you’ll notice that these vectors partition the angle-space into regions that lie between consecutive vectors. Now it’s easy to see that each vector that belongs to the same region will produce the same sign-sequence of the dot-product when computed with those position vectors we had initially. So, we just need one representative from each region with integer-coordinates which we can find by adding each two consecutive vectors: the vector-addition of two vectors lie strictly between them if the vectors are not colinear (parallelogram law).

Finally, these representatives form a list of candidates for the solution vector and we test each whether it satisfies the given constraints (by either maintaining two-pointers as mentioned in the editorial or with a couple of smartly-placed break statements) and if none does, there’s no solution :slightly_smiling_face:

6 Likes

just make the array double size by copying the first n to the next n
Most have them wouldn’t have even thought of this approach, for each vector find 4 vectors.
rotate each vector left by 90 degrees scale it to max possible, take 2 vectors from here subtracting 1 from x or y which makes angle lesser or adding 1 to x or y which makes angle lesser .Same do it for right rotation , now test each of this vector from (i) to (i+n-1) (with while loops and break statements), intuitively it looks O n^2, but performs much faster than O(n logn).

please put the editorial link in problem page