1. 程式人生 > 實用技巧 >【2020杭電多校round2】HDU6774 String Distance

【2020杭電多校round2】HDU6774 String Distance

題目大意

題目連結

對於兩個串\(s,t\)。你可以進行若干次操作。一次操作,你可以:

  • \(s\)\(t\)裡的任意位置插入一個字元。
  • 或在\(s\)\(t\)裡的任意位置刪除一個字元。

我們定義兩個串\(s,t\)的“距離”為,能使\(s,t\)相等的,最少操作次數。

現在給定兩個串\(A[1\dots n],B[1\dots m]\),和\(q\)次詢問。

每次詢問給定兩個整數\(l_i,r_i\) (\(1\leq l_i\leq r_i\leq n\)),求\(A[l_i\dots r_i]\)和整個\(B\)的“距離”。

\(T\)組資料。

資料範圍:\(1\leq T\leq 10\)

\(1\leq n,q\leq 10^5\)\(1\leq m\leq 20\)

本題題解

首先,經過初步分析,兩個串\(s,t\)的距離就等於\(|s|+|t|-2\cdot \text{lcs}(s,t)\)。其中\(\text{lcs}\)表示最長公共序列。於是問題轉化為,每次求\(A[l\dots r]\)\(B\)的最長公共子序列。

注意到\(B\)很小而\(A\)很大,所以要努力使一次詢問的複雜度,與\(A\)的長度(也就是和\(r-l+1\))無關。

可以做一些預處理。設\(dp[i][j][k]\),表示\(A[i\dots n]\)\(B[j\dots m]\)這兩個串,所有長度為\(k\)

公共子序列裡,在\(A\)上結尾最小是多少。換句話說,\(dp[i][j][k]\)就是最小的\(p\geq i\),滿足\(A[i\dots p]\)\(B[j\dots m]\)有長度為\(k\)的公共子序列。如果不存在這樣的\(p\),則令\(dp[i][j][k]=n+1\)

這個預處理挺有技巧的,不是簡單列舉,而需要DP:用已知的來推未知的。我們倒著列舉所有\(i\)。每個\(dp[i][j][k]\),都從\(dp[i+1][\dots][\dots]\)轉移過來。轉移時,有兩種情況:\(i\)在不在這個公共子序列裡,分別考慮一下,兩種情況取個\(\min\)即可。

預處理完\(dp\)

陣列後。考慮回答詢問。對一次詢問,\(A\)的開始位置是\(l\)我們已經知道了。可以暴力列舉\(B\)的開始位置\(j\),再暴力列舉公共子序列長度\(k\),看\(dp[l][j][k]\)是否小於等於\(r\)。如果小於等於\(r\),用\(k\)更新\(\text{lcs}\)的長度即可。

時間複雜度\(O((n+q)m^2)\)

參考程式碼(ps. 本題需要使用fread,可以去這裡粘個板子):

#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,nxt_b[MAXN+5][26];
int f[MAXN+5][MAXM+5][MAXM+5];
char a[MAXN+5],b[MAXM+5];
void get_nxt(char* s,int len,int nxt[MAXN+5][26]){
	static int pos[26];
	for(int i=0;i<26;++i)
		pos[i]=len+1;
	for(int i=len+1;i>=0;--i){
		for(int j=0;j<26;++j)
			nxt[i][j]=pos[j];
		if(i!=0&&i!=len+1)
			pos[s[i]-'a']=i;
	}
}
void solve_case(){
	cin>>(a+1); n=strlen(a+1);
	cin>>(b+1); m=strlen(b+1);
	get_nxt(b,m,nxt_b);
	for(int j=1;j<=m;++j){
		for(int k=1;k<=m;++k){
			f[n+1][j][k]=n+1;
		}
	}
	for(int i=n;i>=1;--i){
		for(int j=1;j<=m;++j){
			f[i][j][1]=f[i+1][j][1];
			if(nxt_b[j-1][a[i]-'a']<=m){
				f[i][j][1]=i;
			}
			for(int k=2;k<=m;++k){
				f[i][j][k]=f[i+1][j][k];
				if(nxt_b[j-1][a[i]-'a']<m)
					ckmin(f[i][j][k],f[i+1][nxt_b[j-1][a[i]-'a']+1][k-1]);
			}
		}
	}
//	while(1){
//		int i,j,k;
//		cin>>i>>j>>k;
//		cout<<f[i][j][k]<<endl;
//	}
	int q;cin>>q;while(q--){
		int l,r;cin>>l>>r;
		int ans=0;
		for(int j=1;j<=m;++j){
			for(int k=1;k<=m-j+1;++k){
				if(f[l][j][k] > r)
					break;
				ckmax(ans,k);
			}
		}
		ans=r-l+1+m-2*ans;
		cout<<ans<<endl;
	}
}
int main() {
	int T;cin>>T;while(T--){
		solve_case();
	}
	return 0;
}