题目大意:
白书练习题
给定一个字符串集合S, 定义P(S)为所有字符串的公共前缀长度与S中字符串个数的乘积, 例如P{000, 001, 0011} = 6,
现在给出n个只包含字符01的串(n <= 50000), 从中选出一个子集合S, 使得P(S)最大, 求最大值
大致思路:
这题比较巧妙, 刚开始是凭感觉写的, 结果一发AC了...后来证明了一下正确性
我的想法就是对n个字符串插入Trie树的时候, 每插入一个节点, 就对当前节点的值加上其深度, 最后所有节点上的值的最大值就是要求的P(S)
正确性的证明放在代码注释里了, 细节见代码注释吧, 表示这题还真是巧妙= =
代码如下:
Result : Accepted Memory : ? KB Time : 186 ms
/* * Author: Gatevin * Created Time: 2015/2/13 16:05:59 * File Name: Mononobe_Mitsuki.cpp */ #include<iostream> #include<sstream> #include<fstream> #include<vector> #include<list> #include<deque> #include<queue> #include<stack> #include<map> #include<set> #include<bitset> #include<algorithm> #include<cstdio> #include<cstdlib> #include<cstring> #include<cctype> #include<cmath> #include<ctime> #include<iomanip> using namespace std; const double eps(1e-8); typedef long long lint; /* * 话说我这个做法在写的时候只是个猜想觉得这样可行 * 就是对n个字符串插入Trie树的时候, 每插入一个节点, 就对当前节点的值加上其深度 * 最后所有节点上的值的最大值就是要求的P(S) * 想这个方法的时候是凭直觉觉得是对的就敲了...结果一发AC了... * 现在再想一下为什么是对的... * 首先按照我这个做法, 最后每个节点代表的就是过这个点的所有字符串的数量乘上这个深度 * 首先很明显的是无论这S个字符串按照什么样的顺序插入最后的结果是一样的(Trie一样) * 那么对于字符串集合T, 如果只插入了T的所有串,如果LCP长度是t, 那么必然存在一个节点其值为t*|T| * 那么对于所有S的子集, 由于插入顺序与最后结果无关, 而且在插入的时候每个点的值都是随着插入串变多不减的 * 所以最终那个最大值的点的值一定不会被破坏, 所以在所有的串被插入之后 * 如果原问题最优解是选取子集T, 那么LCP(T)*|T|一定存在于最后得到Trie树上的某个节点 * 且那个节点的值一定是最大的 * 还是不懂的话就看感觉吧.... */ struct Trie { int next[10000010][2], end[10000010]; int L, root; int ans; int newnode() { next[L][0] = next[L][1] = -1; end[L++] = 0; return L - 1; } void init() { L = 0; ans = 0; root = newnode(); return; } void insert(char *s) { int now = root; int n = strlen(s); for(int deep = 0; deep < n; deep++) { if(next[now][s[deep] - '0'] == -1) next[now][s[deep] - '0'] = newnode(); now = next[now][s[deep] - '0']; end[now] += deep + 1; ans = max(ans, end[now]);//一遍插入一边更新答案 } return; } }; Trie trie; char in[233]; int main() { int t, n; scanf("%d", &t); while(t--) { scanf("%d", &n); trie.init(); while(n--) scanf("%s", in), trie.insert(in); printf("%d\n", trie.ans); } return 0; }