正睿1595 「20聯賽集訓day1」打字機(區間編輯距離查詢問題)


正睿1595 [20聯賽集訓day1]打字機(區間編輯距離查詢問題)

題目大意

給定兩個串 \(S\), \(T\)\(q\) 次詢問,每次查詢 \(S\) 的一個子串 \(S[l,r]\)\(T\) 的編輯距離。

兩串 \(A,B\) 的編輯距離定義為,通過進行插入、刪除、替換的操作(每次操作選擇其中一種),將 \(A\) 變成 \(B\) 所需的最小操作次數。

數據范圍:\(1\leq |S|,q\leq 10^5\)\(1\leq |T|\leq 20\)

本題題解

朴素 DP

拋開多次詢問。只考慮對兩個串 \(A\), \(B\),求他們的編輯距離。

可以做一個朴素 DP。設 \(\text{dp}_0(i,j)\) 表示 \(A[1,i]\)\(B[1,j]\) 的編輯距離(\(\text{dp}_0\) 是為了和后面的其他 DP 區分開來)。則轉移有三種:

\[\begin{cases} \text{dp}_0(i,j) + 1 &\to\text{dp}_0(i + 1,j)\\ \text{dp}_0(i,j) + 1 &\to\text{dp}_0(i,j + 1)\\ \text{dp}_0(i,j) + [A_{i + 1}\neq B_{j + 1}] &\to \text{dp}_0(i + 1, j + 1) \end{cases} \]

一次 DP 的復雜度是 \(O(|A||B|)\)

在本題里,總復雜度是 \(O(q|S||T|)\)

正解

先討論一個一般性的問題。對於一個串 \(A\),考慮它所有后綴與另一個串 \(B\) 的編輯距離。設 \(A\) 的長度為 \(x\) 的后綴與 \(B\) 的編輯距離為 \(h(x)\)。我們發現,函數 \(F(x) = x-h(x)\) 有很好的性質。

  • 首先,\(F(x) = x - h(x)\) 單調不降。這很好證明,因為當 \(x\)\(1\) 時(后綴的長度加 \(1\),即在開頭增加一個字符),\(h(x)\) 最多加 \(1\)(最壞情況就是直接把新加的字符刪掉,用 \(1\) 次操作)。
  • 其次,\(F(x) = x - h(x)\) 的值域是很有限的。因為 \(h(x)\leq x + |B|\),且 \(h(x)\geq x-|B|\),所以 \(-|B|\leq F(x)\leq |B|\)

回到本題。\(S\) 的每個子串都可以表述為 \(S\) 的一個前綴的后綴。所以為了回答詢問,我們要對 \(S\) 的每個前綴,預處理出關於它所有后綴的信息。即,我們要對所有:\(A\)\(S\) 的一個前綴,\(B\)\(T\),的情況,維護出 \(F_{A,B}(x)\)

那么朴素的做法是,設 \(\text{dp}_1(i,j,x)\) 表示 \(F_{S[1,i],T[1,j]}(x)\)。仿照上述的朴素 DP,可以 \(O(1)\) 轉移。最后對於詢問 \(l,r\),答案就是:\(r - l + 1 -\text{dp}_1(r,|T|,r - l + 1)\)。時間復雜度 \(O(|S|^2|T|+q)\)

繼續優化,前面說過,\(F(x)\) 有一個很好的性質,就是值域很小。所以考慮將 DP 的下標與值域互換。重新定義狀態:設 \(\text{dp}_2(i,j,k)\) 表示使得 \(F_{S[1,i],S[1,j]}(x)\leq k\) 的最大的 \(x\)

轉移還是仿照朴素 DP 中的三種情況:

\[\begin{cases} \text{dp}_2(i,j,k) + 1 & \to \text{dp}_2(i + 1,j,k)\\ \text{dp}_2(i,j,k)&\to \text{dp}_2(i,j + 1, k - 1)\\ \text{dp}_2(i,j,k) + 1 &\to \text{dp}_2(i + 1, j + 1, k + [S_{i + 1}=T_{j + 1}]) \end{cases} \]

你可以直接按照編輯距離來理解(和朴素 DP 是類似的)。也可以按照分段函數的合並來理解。在朴素合並分段函數時(也就是 \(\text{dp}_1\)),函數值是對能轉移到它的三種值取 \(\max\)。那新的 DP 中,對於同一個函數值,我們希望它出現的位置越早越好,所以 \(\text{dp}_2\) 的轉移應該是取 \(\min\)

DP 的邊界也比較重要。考慮 \(F_{A,B}(x)\) 函數,\(x\) 的定義域應該是 \([0,|A|]\)。但是為了方便 DP,我們定義 \(F(-1)=-\infty\)。這樣 DP 的邊界就是:\(\forall i,j:\forall k \in[-m-1,-j):\text{dp}_2(i,j,k)=-1\)\(\text{dp}_2(0,0,0)=0\)。其他 \(\text{dp}_2(i,j,k) = \infty\)

DP 的時間復雜度 \(O(|S||T|^2)\)。空間復雜度,用滾動數組優化后,可以做到 \(O(|T|^2)\)

對於詢問 \(l,r\)。我們從小到大枚舉 \(k\),找到第一個使得 \(\text{dp}_2(r,|T|,k)\geq r - l + 1\)\(k\),則答案就是 \(r-l+1-k\)。單次查詢的時間復雜度 \(O(|T|)\)。顯然可以通過二分查找優化到 \(O(\log |T|)\),不過意義不大。

總時間復雜度 \(O(|S||T|^2+q|T|)\)

參考代碼

本題可能需要使用讀入、輸出優化,詳見本博客公告。

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

#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fi first
#define se second
#define SZ(x) ((int)(x).size())

typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;

template<typename T> inline void ckmax(T& x, T y) { x = (y > x ? y : x); }
template<typename T> inline void ckmin(T& x, T y) { x = (y < x ? y : x); }

const int MAXN = 1e5, MAXM = 20;
const int INF = 1e9;
int n, m, q;
char s[MAXN + 5], t[MAXM + 5];

int dp[2][MAXM + 5][MAXM * 2 + 5];
int g[MAXN + 5][MAXM * 2 + 5];
int main() {
	cin >> (s + 1) >> (t + 1) >> q;
	n = strlen(s + 1);
	m = strlen(t + 1);
	int B = m + 1;
	for (int j = 0; j <= m; ++j) {
		for (int k = -j; k <= m; ++k) {
			dp[0][j][k + B] = INF;
		}
		for (int k = -m - 1; k < -j; ++k) {
			dp[0][j][k + B] = -1;
		}
	}
	dp[0][0][B] = 0;
	for (int i = 0; i <= n; ++i) {
		int cur = (i & 1);
		int nxt = (cur ^ 1);
		for (int j = 0; j <= m; ++j) {
			for (int k = -j; k <= m; ++k) {
				dp[nxt][j][k + B] = INF;
			}
			for (int k = -m - 1; k < -j; ++k) {
				dp[nxt][j][k + B] = -1;
			}
		}
		for (int j = 0; j <= m; ++j) {
			for (int k = -m - 1; k <= m; ++k) {
				// cerr << "** " << i << " " << j << " " << k << " " << dp[i][j][k + m] << endl;
				if (i < n)
					ckmin(dp[nxt][j][k + B], dp[cur][j][k + B] + 1);
				if (j < m)
					ckmin(dp[cur][j + 1][k - 1 + B], dp[cur][j][k + B]);
				if (i < n && j < m)
					ckmin(dp[nxt][j + 1][k + (s[i + 1] == t[j + 1]) + B], dp[cur][j][k + B] + 1);
			}
		}
		for (int k = -m; k <= m; ++k) {
			g[i][k + B] = dp[cur][m][k + B];
		}
	}
	for (int tq = 1; tq <= q; ++ tq) {
		int l, r;
		cin >> l >> r;
		int len = r - l + 1;
		for (int k = -m; k <= m; ++k) {
			if (g[r][k + B] >= len) {
				cout << len - k << endl;
				break;
			}
		}
	}
	return 0;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM