2977. 转换字符串的最小成本 II

Powered by:NEFU AB-IN

Link

2977. 转换字符串的最小成本 II

题意

你有两个字符串 source 和 target,它们长度相同并且都由小写字母组成。

还有两个字符串数组 original 和 changed,以及一个整数数组 cost。cost[i] 表示将 original[i] 替换成 changed[i] 的成本。

你需要通过一系列操作将 source 转换成 target,每次操作可以选择 source 中的一个子串 original[j] 并以 cost[j] 的成本将其替换为 changed[j]。但有以下两个条件:

两次操作选择的子串在 source 中的位置不能重叠。
如果两次操作选择了相同的位置,那么它们选择的子串必须完全相同。
你需要找到将 source 转换为 target 的最小成本。如果无法完成转换,返回 -1。

思路

https://leetcode.cn/problems/minimum-cost-to-convert-string-ii/solutions/2577877/zi-dian-shu-floyddp-by-endlesscheng-oi2r/

  1. floyd (优化了) 算字符串的距离
  2. trie 用来转换字符串为整数下标,方便最短路;用来search当前的字符串的所有前缀
    1. 注意这里的search方法我进行修改了,里面也有不符合的,需要自己判断。我的目的是把所有长度求出来,方便zip对应
  3. 记忆化搜索,感觉比dp好写一些,从0开始往后判断,能改到就dfs下一个地方,相同的其实就是边长为0的最短路,是符合条件的,我们也dfs

代码

# 3.8.19 import
import random
from collections import Counter, defaultdict, deque
from datetime import datetime, timedelta
from functools import lru_cache
from heapq import heapify, heappop, heappush, nlargest, nsmallest
from itertools import combinations, compress, permutations, starmap, tee
from math import ceil, comb, fabs, floor, gcd, log, perm, sqrt
from string import ascii_lowercase, ascii_uppercase
from sys import exit, setrecursionlimit, stdin
from typing import Any, Dict, List, Tuple, TypeVar, Union

# Constants
TYPE = TypeVar('TYPE')
N = int(2e5 + 10)
M = int(20)
INF = int(1e12)
OFFSET = int(100)
MOD = int(1e9 + 7)

# Set recursion limit
setrecursionlimit(int(2e9))


class Arr:
    array = staticmethod(lambda x=0, size=N: [x() if callable(x) else x for _ in range(size)])
    array2d = staticmethod(lambda x=0, rows=N, cols=M: [Arr.array(x, cols) for _ in range(rows)])
    graph = staticmethod(lambda size=N: [[] for _ in range(size)])


class Math:
    max = staticmethod(lambda a, b: a if a > b else b)
    min = staticmethod(lambda a, b: a if a < b else b)


class IO:
    input = staticmethod(lambda: stdin.readline().rstrip("\r\n"))
    read = staticmethod(lambda: map(int, IO.input().split()))
    read_list = staticmethod(lambda: list(IO.read()))


class Std:
    class GraphShortestPath:
        def __init__(self, n: int):
            self.n = n
            self.g = Arr.graph(n)
            self.spfa_cache = {}
            self.dijkstra_cache = {}
            self.floyd_cache = None

        def add_edge(self, u: int, v: int, w: int):
            """Add an edge to the graph."""
            self.g[u].append((v, w))

        def spfa(self, s: int) -> List[int]:
            """SPFA (Shortest Path Faster Algorithm) for finding the shortest path in a graph."""
            if s in self.spfa_cache:
                return self.spfa_cache[s]

            dist = Arr.array(INF, self.n)
            st = Arr.array(0, self.n)
            q = deque()

            dist[s] = 0
            q.appendleft(s)
            st[s] = 1

            while q:
                u = q.pop()
                st[u] = 0
                for v, w in self.g[u]:
                    if dist[v] > dist[u] + w:
                        dist[v] = dist[u] + w
                        if st[v] == 0:
                            q.appendleft(v)
                            st[v] = 1

            self.spfa_cache[s] = dist
            return dist

        def dijkstra(self, s: int) -> List[int]:
            """Dijkstra's algorithm for finding the shortest path in a graph."""
            if s in self.dijkstra_cache:
                return self.dijkstra_cache[s]

            dist = Arr.array(INF, self.n)
            st = Arr.array(0, self.n)
            q = []

            dist[s] = 0
            heappush(q, (0, s))

            while q:
                d, u = heappop(q)
                if st[u]:
                    continue
                st[u] = 1
                for v, w in self.g[u]:
                    if dist[v] > dist[u] + w:
                        dist[v] = dist[u] + w
                        heappush(q, (dist[v], v))

            self.dijkstra_cache[s] = dist
            return dist

        def floyd(self) -> List[List[int]]:
            """Floyd's algorithm for finding the shortest paths between all pairs of nodes."""
            if self.floyd_cache is not None:
                return self.floyd_cache

            dist = Arr.array2d(INF, self.n, self.n)
            # Initialize distances with the given edges
            for u in range(self.n):
                for v, w in self.g[u]:
                    dist[u][v] = Math.min(dist[u][v], w)

            # Set the diagonal to zero
            for i in range(self.n):
                dist[i][i] = 0

            # Floyd-Warshall algorithm
            for k in range(self.n):
                for i in range(self.n):
                    if dist[i][k] == INF:  # If there is no path from i to k, skip
                        continue
                    for j in range(self.n):
                        if dist[i][j] > dist[i][k] + dist[k][j]:
                            dist[i][j] = dist[i][k] + dist[k][j]

            self.floyd_cache = dist
            return dist

        def shortest_path(self, x: int, y: int, method: str = 'dijkstra') -> int:
            """Calculate the shortest path from node x to node y using the specified method."""
            if method == 'spfa':
                dist = self.spfa(x)
            elif method == 'dijkstra':
                dist = self.dijkstra(x)
            elif method == 'floyd':
                dist_matrix = self.floyd()
                return dist_matrix[x][y] if dist_matrix[x][y] < INF // 2 else INF
            else:
                raise ValueError("Unsupported method. Use 'spfa', 'dijkstra', or 'floyd'.")

            return dist[y]

    class TrieNode:
        """
        TrieNode class can convert each string into an integer identifier, useful in graph theory.
        It can also quickly process string prefixes, a common feature used in applications like autocomplete and spell checking.
        """
        sid_cnt = 0  # sid counter, representing string index starting from 0

        def __init__(self):
            """Initialize children dictionary and cost. The trie tree is a 26-ary tree."""
            self.children = {}
            self.cost = INF
            self.is_end_of_word = False  # Flag to indicate end of word
            self.sid = -1  # Unique ID for the node, -1 if not assigned

        def add(self, word, cost):
            """Add a word to the trie with the associated cost and return a unique ID."""
            node = self
            for c in word:
                if c not in node.children:
                    node.children[c] = Std.TrieNode()
                node = node.children[c]
            node.cost = Math.min(node.cost, cost)
            node.is_end_of_word = True  # Mark the end of the word
            if node.sid < 0:
                node.sid = self.sid_cnt
                self.sid_cnt += 1
            return node.sid

        def search(self, word: str):
            """Search for prefixes of 'word' in the trie and return their lengths, costs, and sids.

            !! Collects all prefix lengths and their associated costs and sids. 
            Valid matches are those where node.cost != INF and node.sid != -1.
            """
            node = self
            ans = []
            for i, c in enumerate(word):
                if c not in node.children:
                    break
                node = node.children[c]
                ans.append([i + 1, node.cost, node.sid])  # i + 1 to denote length from start
            return ans

        def search_exact(self, word: str, return_type: str = 'cost'):
            """Search for the exact word in the trie and return its cost or unique ID.

            Args:
                word (str): The word to search for.
                return_type (str): The type of value to return. Can be 'cost' or 'sid'.

            Returns:
                int: The cost or unique ID of the word, or INF / -1 if not found.
            """
            node = self
            for c in word:
                if c not in node.children:
                    return INF if return_type == 'cost' else -1
                node = node.children[c]
            if node.is_end_of_word:
                return node.cost if return_type == 'cost' else node.sid
            else:
                return INF if return_type == 'cost' else -1
# ————————————————————— Division line ——————————————————————


class Solution:
    def minimumCost(self, source: str, target: str, original: List[str], changed: List[str], cost: List[int]) -> int:
        trie = Std.TrieNode()
        edges = []
        for u, v, w in zip(original, changed, cost):
            x, y = trie.add(u, 0), trie.add(v, 0)
            edges.append((x, y, w))

        short_path = Std.GraphShortestPath(trie.sid_cnt)
        for u, v, w in edges:
            short_path.add_edge(u, v, w)

        n = len(source)

        @lru_cache(None)
        def dfs(l: int):
            if l >= n:
                return 0
            res = INF
            if source[l] == target[l]:
                res = dfs(l + 1)

            for (len_, _, x), (_, _, y) in zip(trie.search(source[l:]), trie.search(target[l:])):
                if x != -1 and y != -1:
                    res = Math.min(res, short_path.shortest_path(x, y, 'floyd') + dfs(l + len_))
            return res

        ans = dfs(0)

        return ans if ans != INF else -1


# print(Solution().minimumCost("abcdefgh", "acdeeghh", ["bcd", "fgh", "thh"], ["cde", "thh", "ghh"], [1, 3, 5]))
# print(Solution().minimumCost("abcd", "acbe", ["a", "b", "c", "c", "e", "d"], ["b", "c", "b", "e", "b", "e"], [2, 5, 5, 1, 2, 20]))
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

NEFU AB-IN

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值