题意:给出一串单词,每个有一个权值。顺序不变的情况下,删掉一些,使得相邻两单词,前一个是后一个的子串。同时要求使得剩余单词权值和最大。求最大是多少。
分析:
AC自动机+线段树+DP。
这是一个比较复杂的题目,我们分步来讲解。
第一部分,动态规划。
用f[i]表示从第1个单词,到第i个单词,所有剩余单词中包含第i个的情况中最大权值和是多少。
f[i]=max(f[v]+weight[i]),要求第v个单词是第i个单词的子串且v<i。
第二部分,利用AC自动机求所有子串。
fail指针就是找后缀,一个串的子串就是某前缀的后缀。因此我们在建立好自动机之后将一个串重新从root节点开始走,
第三部分,fail树的建立。
我们不是真正的通过fail指针找某串的子串,而是通过fail反向指针找所有以该串为后缀的串。
由于每个节点只有一个fail指针,因此我们可以从root开始利用fail指针的逆指针建立一个fail树。
这个树的意义是,其中每个节点的祖先都是它的后缀。每个节点的子孙都是在该节点的串的前面加入了不同的内容产生的。
我们给fail树中的每个节点v附加一个额外的值f[v](就是第一部分中说的),f[v]的值更新之后会影响到fail树中该串对应节点的子孙的f值。
当我们要计算f[i]时,要分别观察a的所有前缀所在fail树中的值。f[i]=max(f[v]+weight[i]),v是i的所有前缀在fail树中的所有祖先。
现在问题变成了一个,动态改变树中点的权值,并询问某点的祖先中最大值的问题。可以用线段树来解决。
先对fail树进行时间戳标记,这样每个子树对应一个区间,然后每个权值的改变都更新线段书上的一个区间(fail树中的一个子树)即可。
询问时分别询问每个前缀的f[i]取最大即可。
树的时间戳标记模板如下:
void dfs(int u, int parent) { dfn[u][0] = ++dfn_cnt; for (int i = head[u]; i != -1; i = edge[i].next) { int v = edge[i].v; if (v != parent) { dfs(v, u); } } dfn[u][1] = dfn_cnt; }
线段树框架模板如下:
struct SegmentTree { struct Node { int l, r; Node *pleft, *pright; //add the needed variable }tree[MAX_INTERVAL *4]; int node_cnt; void init() { node_cnt = 0; } Node* new_node() { node_cnt++; return tree + node_cnt; } void build_tree(Node *proot, int s, int e) { proot->l = s; proot->r = e; //init the variables if (s == e) { proot->pleft = proot->pright = NULL; return; } int mid = (s + e) / 2; build_tree(proot->pleft = new_node(), s, mid); build_tree(proot->pright = new_node(), mid + 1, e); } void pull_up(Node *proot) { //do something } void push_down(Node *proot) { //do something } void update(Node *proot, int start, int end, int value) { if (start > proot->r || end < proot->l) return; start = max(start, proot->l); end = min(end, proot->r); if (start == proot->l && end == proot->r) { //do something return; } push_down(proot); update(proot->pleft, start, end, value); update(proot->pright, start, end, value); pull_up(proot); } int query(Node *proot, int start, int end) { int ret = proot->value; if (start > proot->r || end < proot->l) return 0; start = max(start, proot->l); end = min(end, proot->r); if (start == proot->l && end == proot->r) { //do something } push_down(proot); ret = max(ret, query(proot->pleft, start, end)); ret = max(ret, query(proot->pright, start, end)); pull_up(proot); return ret; } };
代码如下:
#pragma comment(linker, "/STACK:1024000000,1024000000") #include <cstdio> #include <queue> #include <cstring> using namespace std; #define D(x) const int MAX_CHILD_NUM = 26; const int MAX_NODE_NUM = 3 * (int)1e5 + 10; const int MAX_LEN = 3 * (int)1e5 + 10; const int MAX_N = 2 * (int)1e4 + 10; #define MAX_EDGE_NUM MAX_NODE_NUM * 2 struct Edge { int v, next; Edge() {} Edge(int v, int next):v(v), next(next) {} } edge[MAX_EDGE_NUM]; int head[MAX_NODE_NUM]; int edge_cnt; void init_edge() { memset(head, -1, sizeof(head)); edge_cnt = 0; } void add_edge(int u, int v) { edge[edge_cnt] = Edge(v, head[u]); head[u] = edge_cnt++; } struct Trie { int next[MAX_NODE_NUM][MAX_CHILD_NUM]; int fail[MAX_NODE_NUM]; int count[MAX_NODE_NUM]; int node_cnt; int root; bool vis[MAX_NODE_NUM]; //set it to false void init() { node_cnt = 0; root = newnode(); } int newnode() { for (int i = 0; i < MAX_CHILD_NUM; i++) next[node_cnt][i] = -1; count[node_cnt++] = 0; return node_cnt - 1; } int get_id(char a) { return a - 'a'; } void insert(char buf[], int index) { int now = root; for (int i = 0; buf[i]; i++) { int id = get_id(buf[i]); if (next[now][id] == -1) next[now][id] = newnode(); now = next[now][id]; } count[now] = index; } void build() { queue<int>Q; fail[root] = root; for (int i = 0; i < MAX_CHILD_NUM; i++) if (next[root][i] == -1) next[root][i] = root; else { fail[next[root][i]] = root; Q.push(next[root][i]); } while (!Q.empty()) { int now = Q.front(); Q.pop(); for (int i = 0; i < MAX_CHILD_NUM; i++) if (next[now][i] == -1) next[now][i] = next[fail[now]][i]; else { fail[next[now][i]]=next[fail[now]][i]; Q.push(next[now][i]); } } } void debug() { for(int i = 0;i < node_cnt;i++) { printf("id = %3d,fail = %3d,end = %3d,chi = [",i,fail[i],count[i]); for(int j = 0;j < MAX_CHILD_NUM;j++) printf("%2d",next[i][j]); printf("]\n"); } } void build_fail_tree() { init_edge(); for (int i = 1; i < node_cnt; i++) { add_edge(i, fail[i]); add_edge(fail[i], i); } } }ac; const int MAX_INTERVAL = MAX_LEN; struct SegmentTree { struct Node { int l, r; Node *pleft, *pright; int value; }tree[MAX_INTERVAL *4]; int node_cnt; void init() { node_cnt = 0; } Node* new_node() { node_cnt++; return tree + node_cnt; } void build_tree(Node *proot, int s, int e) { proot->l = s; proot->r = e; proot->value = 0; if (s == e) { proot->pleft = proot->pright = NULL; return; } int mid = (s + e) / 2; build_tree(proot->pleft = new_node(), s, mid); build_tree(proot->pright = new_node(), mid + 1, e); } void pull_up(Node *proot) { } void push_down(Node *proot) { } void update(Node *proot, int start, int end, int value) { if (start > proot->r || end < proot->l) return; start = max(start, proot->l); end = min(end, proot->r); if (start == proot->l && end == proot->r) { proot->value = max(proot->value, value); return; } push_down(proot); update(proot->pleft, start, end, value); update(proot->pright, start, end, value); pull_up(proot); } int query(Node *proot, int start, int end) { int ret = proot->value; if (start > proot->r || end < proot->l) return 0; start = max(start, proot->l); end = min(end, proot->r); if (start == proot->l && end == proot->r) { return ret; } push_down(proot); ret = max(ret, query(proot->pleft, start, end)); ret = max(ret, query(proot->pright, start, end)); pull_up(proot); return ret; } }tree; char st[MAX_LEN]; int pos[MAX_N]; int dfn[MAX_LEN][2]; int dfn_cnt; int n; int weight[MAX_N]; void dfs(int u, int parent) { dfn[u][0] = ++dfn_cnt; for (int i = head[u]; i != -1; i = edge[i].next) { int v = edge[i].v; if (v != parent) { dfs(v, u); } } dfn[u][1] = dfn_cnt; } int work() { int ret = 0; tree.init(); tree.build_tree(tree.tree, 0, dfn_cnt); for (int i = 0; i < n; i++) { int u = ac.root; int temp = 0; for (int j = pos[i]; j < pos[i + 1]; j++) { u = ac.next[u][ac.get_id(st[j])]; temp = max(temp, tree.query(tree.tree, dfn[u][0], dfn[u][0]) + weight[i]); } tree.update(tree.tree, dfn[u][0], dfn[u][1], temp); ret = max(ret, temp); } return ret; } void input() { scanf("%d", &n); int temp = 0; for (int i = 0; i < n; i++) { scanf("%s%d", st + temp, &weight[i]); pos[i] = temp; ac.insert(st + temp, i); int len = strlen(st + temp); temp += len; } pos[n] = temp; } int main() { int t; scanf("%d", &t); for (int i = 1; i <= t; i++) { ac.init(); input(); ac.build(); ac.build_fail_tree(); dfn_cnt = 0; dfs(0, -1); printf("Case #%d: %d\n", i, work()); } return 0; }