Lattice Paths with Obstacles
On a 50 x 50 grid, obstacles are placed at all lattice points (x, y) where x + y is a perfect square and gcd(x, y) > 1. Starting from (0, 0) and moving only right (+1, 0) or up (0, +1), find the nu...
Problem Statement
This archive keeps the full statement, math, and original media on the page.
A square of side length $b > 1$ is rolling around the inside of a larger square of side length $1$, always touching the larger square but without sliding.
Initially the two squares share a common corner. At each step, the small square rotates clockwise about a corner that touches the large square, until another of its corners touches the large square. Here is an illustration of the first three steps for $b = \frac5{13}$.

For some values of $b$, the small square may return to its initial position after several steps. For example, when $b = \frac12$, this happens in $4$ steps; and for $b = \frac5{13}$ it happens in $24$ steps.
Let $F(N)$ be the number of different values of $b$ for which the small square first returns to its initial position within at most $N$ steps. For example, $F(6) = 4$, with the corresponding $b$ values: $$\frac12,\quad 2 - \sqrt 2,\quad 2 + \sqrt 2 - \sqrt{2 + 4\sqrt2},\quad 8 - 5\sqrt2 + 4\sqrt3 - 3\sqrt6,$$ the first three in $4$ steps and the last one in $6$ steps. Note that it does not matter whether the small square returns to its original orientation.
Also $F(100) = 805$.
Find $F(10^8)$.
Problem 935: Lattice Paths with Obstacles
Mathematical Foundation
Theorem 1 (Unrestricted lattice paths). The number of lattice paths from to using unit steps right and up is .
Proof. Each path consists of exactly right steps and up steps in some order. The number of distinct orderings is .
Lemma 1 (Obstacle characterization). A point with is an obstacle if and only if both conditions hold:
- for some non-negative integer , and
- .
The relevant perfect squares are .
Proof. Since , the perfect squares in range are . For each such , we enumerate points with and , then check .
Lemma 2 (Obstacle at origin/destination). : is a perfect square, but is undefined (or conventionally ). We treat as not an obstacle (it is the start). : and , so is an obstacle and the answer is unless the problem excludes the endpoint from the obstacle condition.
Note: The problem formulation must be checked carefully. If is indeed blocked, the answer is .
Theorem 2 (DP correctness). Define as the number of paths from to avoiding obstacles. Then:
- (assuming the origin is not blocked),
- if is an obstacle,
- otherwise (with ).
The value gives the answer.
Proof. Every path to arrives via its last step, which is either from or from . These two cases are mutually exclusive and exhaustive. If is an obstacle, no valid path ends there, so . The base case counts the empty path. By induction on , correctly counts all valid paths.
Editorial
Count lattice paths from (0,0) to (M,M) on an MxM grid using only right and up steps, avoiding blocked points. A point (x,y) is blocked when x+y is a perfect square AND gcd(x,y) > 1. Answer mod 10^9+7. Key observations: dp[x][y] = 0 for blocked cells. We identify obstacles. We then dP. Finally, iterate over x from 0 to M.
Pseudocode
Identify obstacles
DP
for x from 0 to M
for y from 0 to N
else
Complexity Analysis
- Time: for the DP iteration. Each cell requires for the GCD computation. For : operations.
- Space: .
Answer
Code
Each problem page includes the exact C++ and Python source files from the local archive.
#include <bits/stdc++.h>
using namespace std;
int main(){
const int M=50;
const long long MOD=1e9+7;
auto isqs=[](int n){int s=sqrt(n);return s*s==n||(s+1)*(s+1)==n&&(s+1)*(s+1)==n;};
auto ispsq=[](int n)->bool{int s=(int)sqrt((double)n);while(s*s<n)s++;return s*s==n;};
set<pair<int,int>> blk;
for(int x=0;x<=M;x++) for(int y=0;y<=M;y++)
if(ispsq(x+y)&&__gcd(x,y)>1) blk.insert({x,y});
vector<vector<long long>> dp(M+1,vector<long long>(M+1,0));
dp[0][0]=blk.count({0,0})?0:1;
for(int x=0;x<=M;x++) for(int y=0;y<=M;y++){
if(x==0&&y==0) continue;
if(blk.count({x,y})){dp[x][y]=0;continue;}
long long v=0;
if(x>0) v+=dp[x-1][y];
if(y>0) v+=dp[x][y-1];
dp[x][y]=v%MOD;
}
cout<<dp[M][M]<<endl;
return 0;
}
"""
Problem 935: Lattice Paths with Obstacles
Count lattice paths from (0,0) to (M,M) on an MxM grid using only right
and up steps, avoiding blocked points. A point (x,y) is blocked when
x+y is a perfect square AND gcd(x,y) > 1. Answer mod 10^9+7.
Key observations:
- Obstacles form a structured pattern based on number-theoretic conditions.
- Standard DP on the grid: dp[x][y] = dp[x-1][y] + dp[x][y-1], with
dp[x][y] = 0 for blocked cells.
- Without obstacles the answer is C(2M, M); obstacles reduce it.
Methods:
- solve: DP on MxM grid with obstacle detection.
- is_blocked: Check the blocking condition for a point.
- count_obstacles: Count total blocked points for analysis.
Complexity: O(M^2) for DP.
"""
from math import gcd, isqrt
# Helper: check if n is a perfect square
def is_perfect_square(n):
"""Check if n is a perfect square."""
s = isqrt(n)
return s * s == n
# Helper: check if (x, y) is blocked
def is_blocked(x, y):
"""A point is blocked if x+y is a perfect square and gcd(x,y) > 1."""
return is_perfect_square(x + y) and gcd(x, y) > 1
def solve(M=50, MOD=10**9 + 7):
"""Count lattice paths from (0,0) to (M,M) avoiding blocked points."""
blocked = set()
for x in range(M + 1):
for y in range(M + 1):
if is_blocked(x, y):
blocked.add((x, y))
dp = [[0] * (M + 1) for _ in range(M + 1)]
dp[0][0] = 0 if (0, 0) in blocked else 1
for x in range(M + 1):
for y in range(M + 1):
if x == 0 and y == 0:
continue
if (x, y) in blocked:
dp[x][y] = 0
continue
val = 0
if x > 0:
val += dp[x - 1][y]
if y > 0:
val += dp[x][y - 1]
dp[x][y] = val % MOD
return dp[M][M], blocked, dp
def count_obstacles(M):
"""Count and return blocked points in [0,M]x[0,M]."""
blocked = []
for x in range(M + 1):
for y in range(M + 1):
if is_blocked(x, y):
blocked.append((x, y))
return blocked
def unrestricted_paths(M):
"""C(2M, M) = number of lattice paths without obstacles."""
from math import comb
return comb(2 * M, M)
# Verification
# (0,0): x+y=0, perfect square yes, gcd(0,0) undefined but convention: not blocked
# since gcd(0,0) is undefined, dp[0][0] should be 1 (origin is not blocked)
assert not is_blocked(1, 0) # 1+0=1 perfect sq, gcd(1,0)=1, not blocked
assert not is_blocked(0, 1) # same
assert is_blocked(2, 2) # 2+2=4 perfect sq, gcd(2,2)=2>1, blocked
assert not is_blocked(1, 3) # 1+3=4 perfect sq, gcd(1,3)=1, not blocked
# Small grid check: M=2
# Blocked: (2,2) since 4 is perf sq and gcd(2,2)=2
# Paths: (0,0)->(1,0)->(2,0)->(2,1)->(2,2)=blocked, so dp[2][2]=0?
# Actually dp[2][2]=0 since it's blocked.
# Let's check M=3:
ans3, bl3, dp3 = solve(3)
assert (2, 2) in bl3
# Compute answer
ans, blocked, dp_full = solve()
print(f"Answer: {ans}")