题意:
求两个序列不相同的序列个数,n≤1000
分析:
−−我直接正着搞,dp状态很显然,dp[i][j]:=以a[i],b[j]结尾的不相同的序列个数
但事实上非常难转移,存在重复的情况,搞了昨晚一个晚上也没解决
如果反正思考的话,考虑总子序列数(2n+2m−2)减去相同的子序列数,这个题就变得非常显然和简单了
O(nmlognlogm)
1. 考虑dp[i][j]:=以a[i],b[j]结尾的相同的子序列的个数
转移只有一种当a[i]==b[j]时,dp[i][j]=∑dp[x][y](x<i,y<j)+2
这个二维的前缀和,我们可以用二维树状数组来优化就好了
dp[i][j]=sum(i,j−1)+sum(i−1,j)−sum(i−1,j−1)+2,就是去掉右下角的(i,j)这个点
ans相同的子序列个数=sum(n,m)
复杂度为O(nmlognlogm)
代码:
//
// Created by TaoSama on 2015-11-22
// Copyright (c) 2015 TaoSama. All rights reserved.
//
//#pragma comment(linker, "/STACK:1024000000,1024000000")
#include <algorithm>
#include <cctype>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iomanip>
#include <iostream>
#include <map>
#include <queue>
#include <string>
#include <set>
#include <vector>
using namespace std;
#define pr(x) cout << #x << " = " << x << " "
#define prln(x) cout << #x << " = " << x << endl
const int N = 2e3 + 10, INF = 0x3f3f3f3f, MOD = 10000007;
int n, m, u[N], v[N];
int b[N][N], dp[N][N];
void add(int &x, int y) {
x = x + y + MOD;
while(x >= MOD) x -= MOD;
}
void add(int x, int y, int v) {
for(int i = x; i <= n; i += i & -i)
for(int j = y; j <= m; j += j & -j)
add(b[i][j], v);
}
int sum(int x, int y) {
int ret = 0;
for(int i = x; i; i -= i & -i)
for(int j = y; j; j -= j & -j)
add(ret, b[i][j]);
return ret;
}
int cnt = 0;
map<string, int> mp;
int ID(string &s) {
if(mp.count(s)) return mp[s];
mp[s] = ++cnt;
return cnt;
}
void handle(int &n, string &s, int *u) {
n = 0; s += 'A';
string tmp;
for(int i = 0; i < s.size(); ++i) {
if(isalpha(s[i])) {
if(tmp.size()) {
u[++n] = ID(tmp);
tmp.clear();
}
}
tmp += s[i];
}
}
int two[2005] = {1};
string s, t;
int main() {
#ifdef LOCAL
freopen("C:\\Users\\TaoSama\\Desktop\\in.txt", "r", stdin);
// freopen("C:\\Users\\TaoSama\\Desktop\\out.txt","w",stdout);
#endif
ios_base::sync_with_stdio(0);
int T; cin >> T;
int kase = 0;
for(int i = 1; i <= 2000; ++i) two[i] = two[i - 1] * 2 % MOD;
while(T--) {
cin >> s >> t;
cnt = 0; mp.clear();
handle(n, s, u);
handle(m, t, v);
memset(dp, 0, sizeof dp);
memset(b, 0, sizeof b);
for(int i = 1; i <= n; ++i) {
for(int j = 1; j <= m; ++j) {
if(u[i] != v[j]) continue;
add(dp[i][j], sum(i, j - 1) + sum(i - 1, j)
- sum(i - 1, j - 1) + 2);
add(i, j, dp[i][j]);
}
}
int ans = ((two[n] + two[m] - 2 - sum(n, m)) % MOD + MOD) % MOD;
printf("Case %d: %d\n", ++kase, ans);
}
return 0;
}
O(nm)
仔细想想,其实我们可以把前缀和过程直接放在转移里,只要换一下状态
2. 考虑dp[i][j]:=处理到a[i],b[j]的相同的子序列的个数
a[i]==b[j],之前的+之前的加上a[i],b[j]+只有a[i],b[j]
dp[i][j]=2∗dp[i−1][j−1]+2
a[i]!=b[j],把之前所有的加起来,其实就是去掉(i,j)那个点的和上面的一样
dp[i][j]=dp[i][j−1]+dp[i−1][j]−dp[i−1][j−1]
ans相同的子序列个数=dp[n][m]
复杂度为O(nm)
代码:
//
// Created by TaoSama on 2015-11-22
// Copyright (c) 2015 TaoSama. All rights reserved.
//
//#pragma comment(linker, "/STACK:1024000000,1024000000")
#include <algorithm>
#include <cctype>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iomanip>
#include <iostream>
#include <map>
#include <queue>
#include <string>
#include <set>
#include <vector>
using namespace std;
#define pr(x) cout << #x << " = " << x << " "
#define prln(x) cout << #x << " = " << x << endl
const int N = 2e3 + 10, INF = 0x3f3f3f3f, MOD = 10000007;
int n, m, u[N], v[N];
int b[N][N], dp[N][N];
void add(int &x, int y) {
x = x + y + MOD;
while(x >= MOD) x -= MOD;
}
int cnt = 0;
map<string, int> mp;
int ID(string &s) {
if(mp.count(s)) return mp[s];
mp[s] = ++cnt;
return cnt;
}
void handle(int &n, string &s, int *u) {
n = 0; s += 'A';
string tmp;
for(int i = 0; i < s.size(); ++i) {
if(isalpha(s[i])) {
if(tmp.size()) {
u[++n] = ID(tmp);
tmp.clear();
}
}
tmp += s[i];
}
}
int two[2005] = {1};
string s, t;
int main() {
#ifdef LOCAL
freopen("C:\\Users\\TaoSama\\Desktop\\in.txt", "r", stdin);
// freopen("C:\\Users\\TaoSama\\Desktop\\out.txt","w",stdout);
#endif
ios_base::sync_with_stdio(0);
int T; cin >> T;
int kase = 0;
for(int i = 1; i <= 2000; ++i) two[i] = two[i - 1] * 2 % MOD;
while(T--) {
cin >> s >> t;
cnt = 0; mp.clear();
handle(n, s, u);
handle(m, t, v);
memset(dp, 0, sizeof dp);
for(int i = 1; i <= n; ++i) {
for(int j = 1; j <= m; ++j) {
if(u[i] == v[j])
add(dp[i][j], 2 * dp[i - 1][j - 1] + 2);
else
add(dp[i][j], dp[i][j - 1] + dp[i - 1][j] - dp[i - 1][j - 1]);
}
}
int ans = ((two[n] + two[m] - 2 - dp[n][m]) % MOD + MOD) % MOD;
printf("Case %d: %d\n", ++kase, ans);
}
return 0;
}
O(nlogn)
这里就用到LCS和LIS的转化关系了
考虑a[i],b[j]相同的连一条边,如果是公共子序列的话,我们发现边不会交叉,我们可以很惊喜的发现这就是LIS了,问题由二维变成了一维
3. 考虑dp[i]:=以a[i]结尾上升子序列个数
BIT优化下转移过程就好了
ans相同的子序列个数=∑ni=1dp[i]
复杂度为O(nlogn)
代码:
//
// Created by TaoSama on 2015-11-22
// Copyright (c) 2015 TaoSama. All rights reserved.
//
//#pragma comment(linker, "/STACK:1024000000,1024000000")
#include <algorithm>
#include <cctype>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iomanip>
#include <iostream>
#include <map>
#include <queue>
#include <string>
#include <set>
#include <vector>
using namespace std;
#define pr(x) cout << #x << " = " << x << " "
#define prln(x) cout << #x << " = " << x << endl
const int N = 2e3 + 10, INF = 0x3f3f3f3f, MOD = 10000007;
int n, m, c, a[N];
int b[N], dp[N];
int cnt = 0;
map<string, int> mp;
int ID(string &s) {
if(mp.count(s)) return mp[s];
mp[s] = ++cnt;
return -1;
}
void add(int &x, int y) {
x += y;
if(x < 0) x += MOD;
else if(x >= MOD) x -= MOD;
}
void update(int i, int v) {
for(; i <= cnt; i += i & -i) add(b[i], v);
}
int sum(int i) {
int ret = 0;
for(; i; i -= i & -i) add(ret, b[i]);
return ret;
}
void handle(int &n, string &s, bool flag) {
n = c = 0; s += 'A';
string tmp; tmp += s[0];
for(int i = 1; i < s.size(); ++i) {
if(isalpha(s[i])) {
++n;
int id = ID(tmp);
if(flag && ~id) a[++c] = id;
tmp.clear();
}
tmp += s[i];
}
}
int two[2005] = {1};
string s, t;
int main() {
#ifdef LOCAL
freopen("C:\\Users\\TaoSama\\Desktop\\in.txt", "r", stdin);
// freopen("C:\\Users\\TaoSama\\Desktop\\out.txt","w",stdout);
#endif
// ios_base::sync_with_stdio(0);
int T; cin >> T;
int kase = 0;
for(int i = 1; i <= 2000; ++i) two[i] = two[i - 1] * 2 % MOD;
while(T--) {
cin >> s >> t;
cnt = 0; mp.clear();
handle(n, s, false);
handle(m, t, true);
memset(dp, 0, sizeof dp);
memset(b, 0, sizeof b);
for(int i = 1; i <= c; ++i) {
dp[i] = sum(a[i]) + 1;
update(a[i], dp[i]);
}
int ans = ((two[n] + two[m] - 2 - 2 * sum(cnt)) % MOD + MOD) % MOD;
printf("Case %d: %d\n", ++kase, ans);
}
return 0;
}