Minimum path (Codeforces 1031D)
Tags: Greedy, greedy

You are given a matrix of size $$$n \times n$$$ filled with lowercase English letters. You can change no more than $$$k$$$ letters in this matrix.Consider all paths from the upper left corner to the lower right corner that move from a cell to its neighboring cell to the right or down. Each path is associated with the string that is formed by all the letters in the cells the path visits. Thus, the length of each string is $$$2n - 1$$$.Find the lexicographically smallest string that can be associated with a path after changing letters in at most $$$k$$$ cells of the matrix.A string $$$a$$$ is lexicographically smaller than a string $$$b$$$, if the first different letter in $$$a$$$ and $$$b$$$ is smaller in $$$a$$$.

Input: The first line contains two integers $$$n$$$ and $$$k$$$ ($$$1 \le n \le 2000$$$, $$$0 \le k \le n^2$$$) — the size of the matrix and the number of letters you can change.Each of the next $$$n$$$ lines contains a string of $$$n$$$ lowercase English letters denoting one row of the matrix.

Output: Output the lexicographically smallest string that can be associated with some valid path after changing no more than $$$k$$$ letters in the matrix.

Note: In the first sample test case it is possible to change letters 'b' in cells $$$(2, 1)$$$ and $$$(3, 1)$$$ to 'a', then the minimum path contains cells $$$(1, 1), (2, 1), (3, 1), (4, 1), (4, 2), (4, 3), (4, 4)$$$. The first coordinate corresponds to the row and the second coordinate corresponds to the column.

Sample Inputs:
4 2
abcd
bcde
bcad
bcde

5 3
bwwwz
hrhdh
sepsp
sqfaf
ajbvw

7 6
ypnxnnp
pnxonpm
nxanpou
xnnpmud
nhtdudu
npmuduh
pmutsnz

Sample Outputs:
aaabcde

aaaepfafw

aaaaaaadudsnz

Solution (Python):
import decimal
import gc
import heapq
import math
import os
import random
import sys
from collections import Counter, deque, defaultdict
from io import BytesIO, IOBase
import bisect
from types import GeneratorType
BUFSIZE = 8192

class FastIO(IOBase):
    newlines = 0

    def __init__(self, file):
        self._fd = file.fileno()
        self.buffer = BytesIO()
        self.writable = 'x' in file.mode or 'r' not in file.mode
        self.write = self.buffer.write if self.writable else None

    def read(self):
        while True:
            b = os.read(self._fd, max(os.fstat(self._fd).st_size, BUFSIZE))
            if not b:
                break
            ptr = self.buffer.tell()
            (self.buffer.seek(0, 2), self.buffer.write(b), self.buffer.seek(ptr))
        self.newlines = 0
        return self.buffer.read()

    def readline(self):
        while self.newlines == 0:
            b = os.read(self._fd, max(os.fstat(self._fd).st_size, BUFSIZE))
            self.newlines = b.count(b'\n') + (not b)
            ptr = self.buffer.tell()
            (self.buffer.seek(0, 2), self.buffer.write(b), self.buffer.seek(ptr))
        self.newlines -= 1
        return self.buffer.readline()

    def flush(self):
        if self.writable:
            os.write(self._fd, self.buffer.getvalue())
            (self.buffer.truncate(0), self.buffer.seek(0))

class IOWrapper(IOBase):

    def __init__(self, file):
        self.buffer = FastIO(file)
        self.flush = self.buffer.flush
        self.writable = self.buffer.writable
        self.write = lambda s: self.buffer.write(s.encode('ascii'))
        self.read = lambda : self.buffer.read().decode('ascii')
        self.readline = lambda : self.buffer.readline().decode('ascii')
(sys.stdin, sys.stdout) = (IOWrapper(sys.stdin), IOWrapper(sys.stdout))
input = lambda : sys.stdin.readline().rstrip('\r\n')

def isPrime(n):
    if n <= 1:
        return False
    if n <= 3:
        return True
    if n % 2 == 0 or n % 3 == 0:
        return False
    i = 5
    while i * i <= n:
        if n % i == 0 or n % (i + 2) == 0:
            return False
        i = i + 6
    return True

def lcm(a, b):
    return a * b // math.gcd(a, b)

def ciel(a, b):
    x = a // b
    if a % b == 0:
        return x
    return x + 1

def ints_get():
    return map(int, input().strip().split())

def list_get():
    return list(map(int, sys.stdin.readline().strip().split()))

def chars_get():
    return list(map(str, sys.stdin.readline().strip().split()))

def ipn():
    return int(input())

def bootstrap(f, stack=[]):

    def wrappedfunc(*args, **kwargs):
        if stack:
            return f(*args, **kwargs)
        else:
            to = f(*args, **kwargs)
            while True:
                if type(to) is GeneratorType:
                    stack.append(to)
                    to = next(to)
                else:
                    stack.pop()
                    if not stack:
                        break
                    to = stack[-1].send(to)
            return to
    return wrappedfunc

def main():
    (n, k) = ints_get()
    v = []
    for _ in range(n):
        v.append([ord(x) - ord('a') for x in input()])
    out = []
    k += 1
    vis = [[0 for _ in range(n)] for _ in range(n)]
    if v[0][0] == 0:
        vis[0][0] = k
        out.append('a')
    else:
        vis[0][0] = k
        if k == 1:
            out.append(chr(v[0][0] + ord('a')))
        else:
            vis[0][0] -= 1
            out.append('a')
    for s in range(0, 2 * n - 2):
        for i in range(max(0, s - n + 1), min(n, s + 1)):
            j = s - i
            if vis[i][j] == 0:
                continue
            if i + 1 < n:
                vis[i + 1][j] = max(vis[i + 1][j], vis[i][j])
            if j + 1 < n:
                vis[i][j + 1] = max(vis[i][j + 1], vis[i][j])
        t = s + 1
        m = 100
        for i in range(max(0, t - n + 1), min(n, t + 1)):
            j = t - i
            if vis[i][j] == 0:
                continue
            if v[i][j] != 0 and vis[i][j] > 1:
                v[i][j] = 0
                vis[i][j] -= 1
            m = min(m, v[i][j])
        for i in range(max(0, t - n + 1), min(n, t + 1)):
            j = t - i
            if v[i][j] != m:
                vis[i][j] = 0
        out.append(chr(m + ord('a')))
    print(*out, sep='')
    return
if __name__ == '__main__':
    main()

Solution (Python):
from array import array
inf = 1 << 30

def main():
    (n, k) = [int(x) for x in input().split(' ')]
    Matrix = []
    for i in range(n):
        Matrix.append(array('b', [ord(x) for x in input()]))
    dp = [array('l', [inf for j in range(n)]) for i in range(n)]
    direct = [[ord('d') for j in range(n)] for i in range(n)]
    opt = ''
    for s in range(2 * n - 1):
        opchar = chr(ord('z') + 1)
        positions = []
        for i in range(0, s + 1):
            j = s - i
            if j < n and i < n:
                if i > 0 and j > 0:
                    if dp[i - 1][j] < dp[i][j - 1]:
                        dp[i][j] = dp[i - 1][j]
                        direct[i][j] = 'l'
                    else:
                        dp[i][j] = dp[i][j - 1]
                        direct[i][j] = 'd'
                elif i > 0:
                    dp[i][j] = dp[i - 1][j]
                    direct[i][j] = 'l'
                elif j > 0:
                    dp[i][j] = dp[i][j - 1]
                    direct[i][j] = 'd'
                else:
                    dp[i][j] = 0
                    direct[i][j] = 'e'
                if dp[i][j] < k and Matrix[i][j] is not ord('a'):
                    dp[i][j] += 1
                    Matrix[i][j] = ord('a')
                if Matrix[i][j] < ord(opchar) and dp[i][j] <= k:
                    opchar = chr(Matrix[i][j])
        for i in range(0, s + 1):
            j = s - i
            if j < n and i < n:
                if Matrix[i][j] is not ord(opchar):
                    dp[i][j] = inf
    ans = ''
    (a, b) = (n - 1, n - 1)
    while direct[a][b] is not 'e':
        ans += chr(Matrix[a][b])
        if direct[a][b] is 'l':
            a -= 1
        else:
            b -= 1
    ans += chr(Matrix[0][0])
    print(ans[::-1])
main()

Solution (Python):
import os
import sys
from io import BytesIO, IOBase
BUFSIZE = 8192

class FastIO(IOBase):
    newlines = 0

    def __init__(self, file):
        self._fd = file.fileno()
        self.buffer = BytesIO()
        self.writable = 'x' in file.mode or 'r' not in file.mode
        self.write = self.buffer.write if self.writable else None

    def read(self):
        while True:
            b = os.read(self._fd, max(os.fstat(self._fd).st_size, BUFSIZE))
            if not b:
                break
            ptr = self.buffer.tell()
            (self.buffer.seek(0, 2), self.buffer.write(b), self.buffer.seek(ptr))
        self.newlines = 0
        return self.buffer.read()

    def readline(self):
        while self.newlines == 0:
            b = os.read(self._fd, max(os.fstat(self._fd).st_size, BUFSIZE))
            self.newlines = b.count(b'\n') + (not b)
            ptr = self.buffer.tell()
            (self.buffer.seek(0, 2), self.buffer.write(b), self.buffer.seek(ptr))
        self.newlines -= 1
        return self.buffer.readline()

    def flush(self):
        if self.writable:
            os.write(self._fd, self.buffer.getvalue())
            (self.buffer.truncate(0), self.buffer.seek(0))

class IOWrapper(IOBase):

    def __init__(self, file):
        self.buffer = FastIO(file)
        self.flush = self.buffer.flush
        self.writable = self.buffer.writable
        self.write = lambda s: self.buffer.write(s.encode('ascii'))
        self.read = lambda : self.buffer.read().decode('ascii')
        self.readline = lambda : self.buffer.readline().decode('ascii')
(sys.stdin, sys.stdout) = (IOWrapper(sys.stdin), IOWrapper(sys.stdout))
input = lambda : sys.stdin.readline().rstrip('\r\n')
from types import GeneratorType

def bootstrap(f, stack=[]):

    def wrappedfunc(*args, **kwargs):
        if stack:
            return f(*args, **kwargs)
        else:
            to = f(*args, **kwargs)
            while True:
                if type(to) is GeneratorType:
                    stack.append(to)
                    to = next(to)
                else:
                    stack.pop()
                    if not stack:
                        break
                    to = stack[-1].send(to)
            return to
    return wrappedfunc

def RL():
    return map(int, sys.stdin.readline().rstrip().split())

def RLL():
    return list(map(int, sys.stdin.readline().rstrip().split()))

def N():
    return int(input())

def comb(n, m):
    return factorial(n) / (factorial(m) * factorial(n - m)) if n >= m else 0

def perm(n, m):
    return factorial(n) // factorial(n - m) if n >= m else 0

def mdis(x1, y1, x2, y2):
    return abs(x1 - x2) + abs(y1 - y2)
mod = 998244353
INF = float('inf')
from math import factorial
from collections import Counter, defaultdict, deque
from heapq import heapify, heappop, heappush

def main():
    (n, k) = RL()
    arr = [input() for _ in range(n)]
    dp = [[INF] * n for _ in range(n)]
    q = deque()
    pre = 0
    for i in range(n):
        for j in range(n):
            if i == 0 and j == 0:
                dp[0][0] = 1 if arr[0][0] != 'a' else 0
            elif i == 0:
                dp[0][j] = dp[0][j - 1] + (1 if arr[0][j] != 'a' else 0)
            elif j == 0:
                dp[i][0] = dp[i - 1][0] + (1 if arr[i][0] != 'a' else 0)
            else:
                dp[i][j] = min(dp[i - 1][j], dp[i][j - 1]) + (1 if arr[i][j] != 'a' else 0)
            if dp[i][j] <= k:
                pre = max(pre, i + j + 1)
    suf = 2 * n - 1 - pre
    res = ['a'] * pre + ['z'] * suf
    vis = [[0] * n for _ in range(n)]
    q = []
    for i in range(n):
        for j in range(n):
            if dp[i][j] == k and i + j + 1 == pre:
                if i + 1 < n and vis[i + 1][j] == 0:
                    q.append((i + 1, j))
                    vis[i + 1][j] = 1
                if j + 1 < n and vis[i][j + 1] == 0:
                    q.append((i, j + 1))
                    vis[i][j + 1] = 1
    q.sort(key=lambda a: arr[a[0]][a[1]])
    if not q:
        q.append((0, 0))
    for i in range(pre, len(res)):
        newq = []
        res[i] = arr[q[0][0]][q[0][1]]
        for j in range(len(q)):
            now = arr[q[j][0]][q[j][1]]
            if now != res[i]:
                break
            for (nx, ny) in [[1, 0], [0, 1]]:
                (xx, yy) = (nx + q[j][0], ny + q[j][1])
                if xx < n and yy < n and (vis[xx][yy] == 0):
                    vis[xx][yy] = 1
                    while newq and arr[xx][yy] < arr[newq[-1][0]][newq[-1][1]]:
                        newq.pop()
                    if not newq or arr[xx][yy] == arr[newq[-1][0]][newq[-1][1]]:
                        newq.append((xx, yy))
        q = newq
    print(''.join(res))
if __name__ == '__main__':
    main()