• 正睿1595 「20联赛集训day1」打字机(区间编辑距离查询问题)


    正睿1595 「20联赛集训day1」打字机(区间编辑距离查询问题)

    题目大意

    给定两个串 (S), (T)(q) 次询问,每次查询 (S) 的一个子串 (S[l,r])(T) 的编辑距离。

    两串 (A,B) 的编辑距离定义为,通过进行插入、删除、替换的操作(每次操作选择其中一种),将 (A) 变成 (B) 所需的最小操作次数。

    数据范围:(1leq |S|,qleq 10^5)(1leq |T|leq 20)

    本题题解

    朴素 DP

    抛开多次询问。只考虑对两个串 (A), (B),求他们的编辑距离。

    可以做一个朴素 DP。设 ( ext{dp}_0(i,j)) 表示 (A[1,i])(B[1,j]) 的编辑距离(( ext{dp}_0) 是为了和后面的其他 DP 区分开来)。则转移有三种:

    [egin{cases} ext{dp}_0(i,j) + 1 & o ext{dp}_0(i + 1,j)\ ext{dp}_0(i,j) + 1 & o ext{dp}_0(i,j + 1)\ ext{dp}_0(i,j) + [A_i eq B_j] & o ext{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))

    那么朴素的做法是,设 ( ext{dp}_1(i,j,x)) 表示 (F_{S[1,i],T[1,j]}(x))。仿照上述的朴素 DP,可以 (O(1)) 转移。最后对于询问 (l,r),答案就是:(r - l + 1 - ext{dp}_1(r,|T|,r - l + 1))。时间复杂度 (O(|S|^2|T|+q))

    继续优化,前面说过,(F(x)) 有一个很好的性质,就是值域很小。所以考虑将 DP 的下标与值域互换。重新定义状态:设 ( ext{dp}_2(i,j,k)) 表示使得 (F_{S[1,i],S[1,j]}(x)leq k) 的最大的 (x)

    转移还是仿照朴素 DP 中的三种情况:

    [egin{cases} ext{dp}_2(i,j,k) + 1 & o ext{dp}_2(i + 1,j,k)\ ext{dp}_2(i,j,k)& o ext{dp}_2(i,j + 1, k - 1)\ ext{dp}_2(i,j,k) + 1 & o ext{dp}_2(i + 1, j + 1, k + [S_i=T_j]) end{cases} ]

    你可以直接按照编辑距离来理解(和朴素 DP 是类似的)。也可以按照分段函数的合并来理解。在朴素合并分段函数时(也就是 ( ext{dp}_1)),函数值是对能转移到它的三种值取 (max)。那新的 DP 中,对于同一个函数值,我们希望它出现的位置越早越好,所以 ( ext{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): ext{dp}_2(i,j,k)=-1)( ext{dp}_2(0,0,0)=0)。其他 ( ext{dp}_2(i,j,k) = infty)

    DP 的时间复杂度 (O(|S||T|^2))。空间复杂度,用滚动数组优化后,可以做到 (O(|T|^2))

    对于询问 (l,r)。我们从小到大枚举 (k),找到第一个使得 ( ext{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;
    }
    
  • 相关阅读:
    微信h5下拉隐藏网页,还有取消页面滑动
    vuejs中使用递归嵌套组件
    运行gitbook init命令报错及问题解决办法
    利用python生成gitbook目录文件
    通过Appium日志,分析其运行原理
    字符串两两更换位置
    Dockerfile启动的程序,内存不断增长问题
    测试流程优化
    APP测试面试题(一)
    关于面试总结13-app测试面试题
  • 原文地址:https://www.cnblogs.com/dysyn1314/p/14014766.html
Copyright © 2020-2023  润新知