Distinct Subsequences
原文链接 http://huiming.io/2013/07/01/distinct-subsequences.html
注:以下为加速网络访问所做的原文缓存,经过重新格式化,可能存在格式方面的问题,或偶有遗漏信息,请以原文为准。
分析:先考虑一个例子:
x=babgbag z=bag
答案是5,如何数数?
假设函数times(x, z)返回z在x中的次数(z可以是字符串也可以是字符——后者相当容易处理),容易得到递归解(伪代码):<!--more-->
times(x, z) {
if z.size == 1
return times(x, z[0]) //寻找字符z[0]在串x中出现的次数
s = 0
for i = 0; i < x.size; i++
if x[i] == z[0]
//s[i, j]表示s从索引i开始到j结束(包括j在内)的子串,索引-1的位置指向串的最后一个字符
s += times(x[i + 1, -1], z[1, -1])
return s
}
以上代码在每次递归之前都要生成两个新的子串,在此我们做一点优化:如果我们从串的尾部向前处理,则可以仅用子串的长度值来表示子串。由此得到
times(x, z, i, j) { //i,j分别为x,z从0开始的子串长度
if j == 1
return times(x, i, z[0]) //寻找字符z[0]在串x子串中出现的次数
s = 0
for k = i - 1; k >= 0; k--
if x[k] == z[j - 1]
s += times(x, z, k, j - 1)
return s
}
如果我们以一个二维表格T来表示times计算的结果,则T[i][j]就是我们想要的答案,而且我们现在已经有了递推公式:
T[i][j] = {
s = 0
for k = i - 1; k >= 0; k--
if x[k] == z[j - 1]
s += T[k][j - 1]
return s
}
和初始值:
T[i][1] = times(x, i, z[0])
以及:
T[i][j] = 0 when i == 0 || j == 0 || i < j
但是在我们着手计算之前,结合表格仔细考虑一下递推公式,还可以消除一些重复计算:
T[i][j] = {
s = T[i - 1][j]
if z[j - 1] == x[i - 1]
s += T[i - 1][j - 1]
return s
}
现在足够简单了。不过还有最后一个问题:因为x、z的长度最长分别可达10000和100,所以最多可以有C(10000, 100)种组合方法(10000个数里选出100个),这是一个天文数字,必须用大数运算。
#include <iostream>
#include <string>
#include <vector>
#include "bigint.h"
using namespace std;
BigInt times(const string & x, const string & z) {
if (x.empty() || z.empty() || x.size() < z.size()) {
return BigInt();
}
int rows = x.size() + 1;
int cols = z.size() + 1;
vector<vector<BigInt> > T(rows, vector<BigInt>(cols));
int i, j;
int c = 0;
char ch = z[0];
for (i = 0; i < x.size(); i++) {
if (x[i] == ch)
c++;
T[i + 1][1] = c;
}
for (i = 2; i < rows; i++) {
for (j = 2; j < cols && j <= i; j++) {
BigInt s = T[i - 1][j];
if (z[j - 1] == x[i - 1]) {
s += T[i - 1][j - 1];
}
T[i][j] = s;
}
}
return T[x.size()][z.size()];
}
int main() {
int N = 0;
cin >> N;
cin.ignore();
for (int i = 0; i < N; i++) {
string x, z;
cin >> x >> z;
cout << times(x, z) << endl;
}
return 0;
}