題解/算法 {8028. 执行操作使两个字符串相等}
@LINK: https://leetcode.cn/problems/apply-operations-to-make-two-strings-equal/
;
令vector<int> A
為: 如果S1[i] != S2[i]
則執行A.push_back( i)
(即A裡是所有(需要操作的下標), 而且是遞增的);
因此 此時我們只需要關注這個A序列, 清空這個A序列 就是我們的目的;
對於操作1:(選擇A裡的兩個元素 代價是X 將他倆扔出去), 操作2:(選擇A裡的兩個元素a,b
代價是|a-b|
把他倆扔出去) (這個操作2是我們轉換後的形式 因為對於[0,1,2,3]
他可以變成(01) (12) (23)
即操作3次3-0
次);
此時問題轉換為: 通過這兩個操作 將序列A清空的最小代價;
顯然如果A.size()
為奇數 這是無解的;
性質1: 操作2所選擇的元素a,b
一定是A裡相鄰的;
.
反證, 比如x...a,c,b...x
你選擇的是操作2(ab)
那麼這個c
要麼是操作1cx
(此時變成操作2(ac) 操作1(bx)
代價會變小), 要麼是操作2cx
(此時變成操作2(ac) 操作2(bx)
代價會變小);
性質2: 操作1所選擇的元素 (a1,b1) (a2,b2) ...
, 一定可以滿足b1 < a2, b2 < a3, ...
;
.
因為 操作1的代價是固定的X, 你交換他們的次序 比如(1,5) (2,10)
和 (1,2) (5,10)
是完全一樣的代價;
此時最終的A序列是形如 [ a, b, c, (d,e), f, (g,h), (i,j), k, l]
((d,e)
表示操作2);
{ 錯誤
貪心; 從A開頭開始 如果A[i+1] - A[i] < X
那麼就使用操作2;
這是錯誤的… 比如[0, 2, 3, 6]
你會選擇[(0,2), 3, 6]
但答案是[0, (2,3), 6]
;
你可能認為 那再反向的處理一遍, 也是錯誤的… 當你選擇貪心時 沒有十分把握時 就是在冒險…
}
其實這很容易轉換為DP問題;
.
你可能定義為(i,j): 前i個元素裡 有j個元素是沒被選擇 即有(i-j)個元素是通過操作2進行了配對 的最小代價
這樣DP定義 就複雜很多 因為你只是對操作2進行了DP 沒考慮操作1, 其實你可以同時考慮這兩個操作;
最好的定義DP是: (i): A[0...i]這些元素(i為奇數) 通過操作1|2 將這些元素進行配對的最小代價
, 你可能疑問 對於[a, (b,c), d]
這種情況 你的DP 在DP[c]
時 並沒有記錄a, (b,c)
這個狀態 如何轉換呢? 這確實是沒法進行轉義的, 但是結合上面講的性質1|2, 這個DP比較特殊;
對於A[0...i] (共偶數個元素)
此時你要對A[i]
進行配對, 要麼A[i-1],A[i]
配對 前面為DP[i-2]
, 要麼是A[...,j-1] A[j], A[j+1,...,i-1], A[i]
此時根據性質1|2 中間的A[j+1,...,i-1]
他一定是形如(A[j+1] A[j+2]), (A[j+2] A[j+3]), ..., (A[i-2] A[i-1])
因此 這個值可以暴力的計算, 在你往前枚舉j
時 同時維護這個值;
@DELI;
#代碼#
int minOperations(string S1, string S2, int X) {
vector< int> A;
int N = S1.size();
FOR_( i, 0, N-1){
if( S1[i] != S2[i]){ A.push_back( i);}
}
if( A.size() & 1){ return -1;}
N = A.size();
if( N == 0){ return 0;}
vector< int> DP( N);
for( int i = 1; i < N; i += 2){ // 只遍歷偶數
DP[ i] = min( X, A[i] - A[i-1]);
if( i-1 > 0){ DP[ i] += DP[ i-2];}
int sum = 0;
for( int j = i - 3; j >= 0; j -= 2){ // 注意 不是`j-=3`;
sum += (A[j+2] - A[j+1]);
int ANS = sum + X;
if( j > 0){ ANS += DP[j - 1];}
MinSelf_( DP[i], ANS);
}
}
return DP[ N-1];
}