Topcoder 14531 Softmatch
题目链接
Vjudge Softmatch Topcoder 14531 Softmatch
题目大意
有一个长为 \(n\) 的主串 \(S\) 和 \(K\) 个长度 \(\leq n\) 的模式串 \(\{p_i\}\),\(S\) 由 a 和 b 组成, \(p_i\) 由 1,2,3,4 组成,给定 \(S\) 串后我们可以选择其中一些字符改为大写,即 a->A 或 b->B,有如下的匹配规则:
a 匹配 0 和 1,A 匹配 2 和 3 。
b 匹配 0 和 2,B 匹配 1 和 3 。
设 \(T\) 在 \(S\) 中的匹配次数为 \(f(T)\),求 \(\max\{\sum_{i=1}^K f(p_i)\}\)。
\(1\leq n\leq 50\), \(1\leq K\leq 5\)
思路
\(O(n^4K^2)\) 做法
考虑 \(dp\),注意到 \(S\) 每一位的两种选择互为补集,如果我们已经确定了一个串在位置 \(i\) 处匹配了 \(k\) 位,那么便可以直接还原出 \(S\) 在那 \(k\) 位的大小写选择情况,由于如果要 \(dp\) 转移,我们是必须要知道 \(S\) 这附近几位是什么样子的,于是设计这样的 \(dp\) 状态:
设 \(dp_{i,j,k}\) 为当前考虑到了 \(S\) 的第 \(i\) 位,和 \(S\) 匹配长度最大的串是 \(p_j\),它的匹配长度是 \(k\) ,此时完成的匹配数量最大值。由于匹配长度最大,这 \(k\) 位已经包含了我们所有需要知道的信息,在到达这一状态时,把 \(S\) 的这 \(k\) 位还原出来,随后枚举下一位是选小写还是大写,把两种情况下的 \(k+1\) 位主串的后缀和 \(K\) 个模式串的前缀进行匹配,得到各自的最大匹配长度,然后就可以知道该决策下第 \(i+1\) 位完成了多少次匹配了。
状态数 \(O(n^2K)\),暴力转移一次是 \(O(n^2K)\) 的,所以时间复杂度为 \(O(n^4K^2)\) 。
\(O(n^3K^2)\) 做法
注意到上面每次的转移有重复可以利用的地方,有 \(O(nK)\) 个长度至多为 \(n\) 的确定的 \(S\) 片段,把他们预处理出来,再和 \(K\) 个串前后缀匹配,预处理 \(O(n^3K^2)\),\(dp\) 的时间复杂度降到了 \(O(n^2K^2)\) 。
\(O(n^3K)\) 做法
可以发现上面的做法还是有一点暴力的成分在里面的,复杂度瓶颈在于前后缀匹配这里。
不要考虑匹配长度这件事了,我们直接快进到匹配完成这件事,毕竟最终答案也只要完成匹配的数量,而匹配的最大数量也就 \(nK\) 个,背后最多对应着 \(O(nK)\) 中 \(S\) 不同选择方式的片段。设 \(0\) 为 \(S\) 这一位选择小写,\(1\) 为这一位选择大写,这样总共会有 \(nK\) 个 01串,将它们插入 \(AC\) 自动机里,重新来 \(dp\) 。
设 \(dp_{i,j}\) 为考虑到了 \(S\) 的第 \(i\) 位,目前位于 \(AC\) 自动机的节点 \(j\) 时的最大匹配数。先预处理出每个模式串在不同位置完成匹配对应 \(AC\) 自动机上的节点,设为 \(match_{i,j}\),另设 \(cnt_i\) 为第 \(j\) 个节点当前对应了多少个完成的匹配,\(cnt\) 很好计算,先在每个模式串直接匹配(即 \(match_{i,j}\))的节点记上一个 \(1\),然后由浅到深扫一遍 \(AC\) 自动机,每个节点再从其 \(fail\) 节点处转移一次即可。
于是就可以直接 \(dp\) 转移了!枚举下一位是 \(0\) 还是 \(1\),然后往下一个节点直接转移即可,设枚举的这一位是 \(p\),即
\(AC\) 自动机共 \(O(n^2K)\) 个节点,\(dp\) 状态数 \(O(n^3K)\),转移 \(O(1)\),\(cnt\) 计算复杂度 \(O(n^3K)\),从而总时间复杂度 \(O(n^3K)\) 。在 \(1\leq N,K\leq 100\) 的数据上虽然理论计算量上限 \(1e8\),但实际上贴不到上限,\(code\) 实际表现极限数据 \(421\;ms\) 。
Code
题目原意可能是想要大家找到一种多项式时间复杂度的做法即可,但是深挖下去,其实优化的空间还挺多的!借助这个特性,我把它出成了模拟赛的题,设了很多档部分分,然而结果没有人在这题上获得 \(>0\) 的分数。。
#include<iostream>
#include<vector>
#define rep(i,a,b) for(int i = (a); i <= (b); i++)
#define per(i,b,a) for(int i = (b); i >= (a); i--)
#define N 55
#define K 6
#define Inf 0x3f3f3f3f
using namespace std;
struct Aho_Corasick_Automaton{
struct node{
int cnt, id;
node *son[2], *fail;
node(){
son[0] = son[1] = fail = 0;
cnt = id = 0;
}
} *root = new node, *q[N*N*K], *match[K][N];
int cnt, automaton[N*N*K][2];
node* insert(vector<int> choice){
node *x = root;
for(int k : choice){
if(x->son[k] == 0) x->son[k] = new node;
x = x->son[k];
}
return x;
}
void build(){
q[1] = root, root->id = 1, cnt = 1;
rep(i,0,1) if(root->son[i])
root->son[i]->fail = root, q[++cnt] = root->son[i], q[cnt]->id = cnt;
rep(i,2,cnt) rep(j,0,1) if(q[i]->son[j]){
q[++cnt] = q[i]->son[j];
node *nxt = q[i]->fail;
while(nxt != root && nxt->son[j] == 0) nxt = nxt->fail;
if(nxt->son[j]) nxt = nxt->son[j];
q[cnt]->id = cnt, q[cnt]->fail = nxt;
}
rep(i,1,cnt) rep(j,0,1){
if(q[i]->son[j]) automaton[i][j] = q[i]->son[j]->id;
else{
if(i == 1) automaton[1][j] = 1;
else automaton[i][j] = automaton[q[i]->fail->id][j];
}
}
}
void clear(){ rep(i,1,cnt) q[i]->cnt = 0; }
void prepare(){ rep(i,2,cnt) q[i]->cnt += q[i]->fail->cnt; }
} AC;
class Softmatch{
public:
int n, m, dp[N*N*K], pre[N*N*K];
int make_choice(char a, char b){
if(a == 'a') return b >= '2';
else return b == '1' || b == '3';
}
bool Max(int &a, int b){ return a < b ? a = b, 1 : 0; }
int count(string S, vector<string> patterns){
n = S.size(), m = patterns.size();
S = " "+S;
rep(i,0,m-1){
int len = patterns[i].size();
rep(j,1,n-len+1){
vector<int> choice;
rep(k,0,len-1) choice.push_back(make_choice(S[j+k], patterns[i][k]));
AC.match[i][j+len-1] = AC.insert(choice);
}
}
AC.build();
rep(i,1,AC.cnt) dp[i] = -Inf;
dp[1] = 0;
rep(i,1,n){
AC.clear();
rep(j,0,m-1) if(AC.match[j][i]) AC.match[j][i]->cnt++;
AC.prepare();
rep(j,1,AC.cnt) pre[j] = dp[j], dp[j] = -Inf;
rep(j,1,AC.cnt) if(pre[j] >= 0){
rep(k,0,1){
int nxt = AC.automaton[j][k];
Max(dp[nxt], pre[j]+AC.q[nxt]->cnt);
}
}
}
int ans = 0;
rep(i,1,AC.cnt) Max(ans, dp[i]);
return ans;
}
} solve;
int main(){
string a, b;
vector<string> read;
int k;
cin>>a>>k;
rep(i,1,k) cin>>b, read.push_back(b);
cout<< solve.count(a, read) <<endl;
return 0;
}