好题,学到新姿势!
题意:给两个字符串 a 和 b ,b可以进行变换,规则是可以任意交换相邻两个字符的位置,但是不可以有交叉(例如3和4交换,5和6交换 互不影响,但是2和3,3和4就不可以)。求a中每一个位置能不能匹配b或b变幻得到的子串。
题解:考虑dp。dp[i][j][k]表示a[i]和b[j]匹配,k为1表示j未做交换,k=0表示j和j-1进行交换,k=2表示j和j+1进行交换。
这样写出来的代码:
#include <iostream> #include <cstdio> #include <vector> #include <cstring> using namespace std; const int N = 10005; const int M = 5005; char a[N], b[N]; int ans[N]; bool dp[N][M][3]; // 0和前面的交换 1不交换 2和后面的交换 int main() { //freopen("in", "r", stdin); int T; int la, lb; scanf("%d", &T); while (T--) { scanf("%d%d", &la, &lb); scanf("%s%s", a+1, b+1); memset(dp, false, sizeof dp); for (int i = 0; i <= la; ++i) dp[i][0][1] = true; for (int i = 1; i <= la; ++i) { for (int j = 1; j <= lb; ++j) { dp[i][j][0] = (dp[i-1][j-1][2] && a[i] == b[j-1]); dp[i][j][1] = (dp[i-1][j-1][1] && a[i] == b[j]) || (dp[i-1][j-1][0] && a[i] == b[j]); dp[i][j][2] = (dp[i-1][j-1][1] && a[i] == b[j+1]) || (dp[i-1][j-1][0] && a[i] == b[j+1]); } if (dp[i][lb][1] || dp[i][lb][0]) ans[i] = 1; else ans[i] = 0; } for (int i = lb; i <= la; ++i) printf("%d", ans[i]); for (int i = 1; i < lb; ++i) printf("0"); printf("\n"); } return 0; }
然而很明显,这会爆内存。
容易想到使用滚动数组,因为递推时只与前一个状态有关。
加了滚动数组的代码:
#include <iostream> #include <cstdio> #include <vector> #include <cstring> using namespace std; const int N = 100005; const int M = 5005; char a[N], b[N]; int ans[N]; bool dp[2][M][3]; // 0和前面的交换 1不交换 2和后面的交换 int main() { //freopen("in", "r", stdin); int T; int la, lb; scanf("%d", &T); while (T--) { scanf("%d%d", &la, &lb); scanf("%s%s", a+1, b+1); memset(dp, false, sizeof dp); dp[0][0][1] = dp[1][0][1] = true; int now = 1; for (int i = 1; i <= la; ++i) { for (int j = 1; j <= lb; ++j) { dp[now][j][0] = (dp[now^1][j-1][2] && a[i] == b[j-1]); dp[now][j][1] = ((dp[now^1][j-1][1] || dp[now^1][j-1][0]) && a[i] == b[j]); dp[now][j][2] = ((dp[now^1][j-1][1] || dp[now^1][j-1][0]) && a[i] == b[j+1]); } if (dp[now][lb][1] || dp[now][lb][0]) ans[i] = 1; else ans[i] = 0; now ^= 1; } for (int i = lb; i <= la; ++i) printf("%d", ans[i]); for (int i = 1; i < lb; ++i) printf("0"); printf("\n"); } return 0; }
很棒,这回不MLE了,但是TLE。
其实O(N*M)的复杂度,T了倒也很正常,但是据说各种姿势的暴力都能过,为甚么这就过不去>_<
继续优化。
---
学习一下bitset。
可以当作一个bool型数组考虑,bitset<N> bs; 可以考虑成一个数组bool bs[N]。
相关操作:
bs.set(); 全部置1,bs.reset()全部置0;
bs.set(pos);等价于bs[pos]=1,bs.reset(pos)等价于bs[pos]=0;
最重点的来了,bitset<N> a, b;
a和b可以直接进行操作,
!a //按位取反 a^b //按位异或 a|b //按位或 a&b //按位与 a=b<<3 //整体移位 a.count(); //a中1的个数
bitset有什么用呢(也许还有其他的用处,这里讲的是本题用到的)
如果有一个bool数组 a[N] 和b[N] 把每一个位异或的话,一定是
for (int i = 0; i < N; ++i) c[i] = a[i] ^ b[i];
但是如果用bitset直接a^b的话,只需要O(N/机器字节数)
这样可以实现常数优化。
---
这和这道题有什么关系呢?
观察dp方程,
dp[i][j][1] = ((dp[i-1][j-1][1] || dp[i-1][j-1][0]) && a[i] == b[j]);
先考虑一个,其余两个同理。
可以发现dp[i]只与dp[i-1]有关。
那么把dp数组定义为bitset<N> dp[2][3] ,就可以把dp方程写成dp[j][1] = ((dp[j-1][1] | dp[j-1][0]) << 1);
这里应该很好理解,也很神奇,其中左移是因为这里j-1求出的是j。
但是a[i]==b[j]要怎么处理呢?
因为前面是用一个bitset处理的,我们希望把a[i]==b[j]也化成一个bitset的形式,也就是对于每一个j,有一个bitset<N> bs,bs[i]其中来表示a[i]==b[j]的值,但是这样又是N*M,明显不可能的,其实不需要对于每一个j都有一个bitset,因为一共有26个字母,于是只需对每一个字母求出,即bitset<N>26,然后使用b[j]那个的字母bitset就可以了。
至于滚动数组就很好实现了,
这题正解反而卡常数卡的厉害,姿势不对很容易超时,没人性!! T^T
AC代码(3166MS)
#include <cstdio> #include <bitset> #include <iostream> #include <cstring> using namespace std; const int N = 100005; const int M = 5005; char a[N]; char b[M]; //bool dp[N][M][3]; 0和前面的交换 1不交换 2和后面的交换 bitset<N> dp[2][3]; bitset<N> w[30]; // 记录对于每个字母 p[i]是否为这个字母 int main() { //freopen("in", "r", stdin); int T; int la, lb; scanf("%d", &T); while (T--) { scanf("%d%d", &la, &lb); scanf("%s%s", a, b); for (int i = 0; i < 26; ++i) w[i].reset(); for (int i = 0; i < la; ++i) w[a[i]-‘a‘][i] = 1; for (int i = 0; i < 2; ++i) for (int j = 0; j < 3; ++j) dp[i][j].reset(); dp[0][1] = w[b[0]-‘a‘]; if (lb > 1) dp[0][2] = w[b[1]-‘a‘]; int now = 0; for (int j = 1; j < lb; ++j) { now ^= 1; dp[now][0] = ((dp[now^1][2]) << 1) & w[b[j-1]-‘a‘]; dp[now][1] = ((dp[now^1][0] | dp[now^1][1]) << 1) & w[b[j]-‘a‘]; if (j < lb - 1) dp[now][2] = ((dp[now^1][0] | dp[now^1][1]) << 1 ) & w[b[j+1]-‘a‘]; } for (int i = 0; i < la; ++i) { if (dp[now][0][i+lb-1] || dp[now][1][i+lb-1]) printf("1"); else printf("0"); } puts(""); } return 0; }