题目大意:
白书练习题
给定一个字符串集合S, 定义P(S)为所有字符串的公共前缀长度与S中字符串个数的乘积, 例如P{000, 001, 0011} = 6,
现在给出n个只包含字符01的串(n <= 50000), 从中选出一个子集合S, 使得P(S)最大, 求最大值
大致思路:
这题比较巧妙, 刚开始是凭感觉写的, 结果一发AC了...后来证明了一下正确性
我的想法就是对n个字符串插入Trie树的时候, 每插入一个节点, 就对当前节点的值加上其深度, 最后所有节点上的值的最大值就是要求的P(S)
正确性的证明放在代码注释里了, 细节见代码注释吧, 表示这题还真是巧妙=
/*
* 话说我这个做法在写的时候只是个猜想觉得这样可行
* 就是对n个字符串插入Trie树的时候, 每插入一个节点, 就对当前节点的值加上其深度
* 最后所有节点上的值的最大值就是要求的P(S)
* 想这个方法的时候是凭直觉觉得是对的就敲了...结果一发AC了...
* 现在再想一下为什么是对的...
* 首先按照我这个做法, 最后每个节点代表的就是过这个点的所有字符串的数量乘上这个深度
* 首先很明显的是无论这S个字符串按照什么样的顺序插入最后的结果是一样的(Trie一样)
* 那么对于字符串集合T, 如果只插入了T的所有串,如果LCP长度是t, 那么必然存在一个节点其值为t*|T|
* 那么对于所有S的子集, 由于插入顺序与最后结果无关, 而且在插入的时候每个点的值都是随着插入串变多不减的
* 所以最终那个最大值的点的值一定不会被破坏, 所以在所有的串被插入之后
* 如果原问题最优解是选取子集T, 那么LCP(T)*|T|一定存在于最后得到Trie树上的某个节点
* 且那个节点的值一定是最大的
* 还是不懂的话就看感觉吧....
*/
代码如下:
#include
#include
#include
#include
using namespace std;
const int maxnode = 4000 * 100 + 10;
const int sigma_size = 26;
int maxx=-1;
// 字母表为全体小写字母的Trie
struct Trie {
int ch[maxnode][sigma_size];
int val[maxnode];
int sz; // 结点总数
void clear() { sz = 1; memset(ch[0], 0, sizeof(ch[0])); memset(val, 0, sizeof(val));} // 初始时只有一个根结点
int idx(char c) { return c - '0'; } // 字符c的编号
// 插入字符串s,附加信息为v。注意v必须非0,因为0代表“本结点不是单词结点”
void insert(const char *s) {
int u = 0, n = strlen(s);
for(int i = 0; i < n; i++) {
int c = idx(s[i]);
if(!ch[u][c]) { // 结点不存在
memset(ch[sz], 0, sizeof(ch[sz]));
val[sz] = 0;
ch[u][c] = sz++; // 新建结点
}
u = ch[u][c]; // 往下走
val[u]+=(i+1);
maxx=max(maxx,val[u]) ;
}
return;
//val[u]+=n;
//maxx=max(maxx,val[u]) ;
//val[u] = v; // 字符串的最后一个字符的附加信息为v
}
};
#include
const int maxl = 300000 + 10; // 文本串最大长度
const int maxw = 4000 + 10; // 单词最大个数
const int maxwl = 100 + 10; // 每个单词最大长度
const int MOD = 20071027;
int d[maxl], len[maxw], S;
char text[maxl], word[maxwl];
Trie trie;
int main() {
//freopen("in.txt","r",stdin);
int tt,S;
cin>>tt;
while(tt--) {
maxx=-1;
cin>>S;
trie.clear();
for(int i = 1; i <= S; i++) {
scanf("%s", word);
trie.insert(word);
}
cout<