HDU: 1247.Hat’s Words

本文介绍了一个使用C语言实现的程序,该程序利用Trie树数据结构来检查一个单词是否可以被拆分为两个存在于字典中的子串。通过构建Trie树,程序能够高效地查找和插入字符串。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Ctrl+Z结束输入

#include <stdio.h>
#include <string.h>
struct st{
	char word[100];
}w[50010];

const int maxnode = 1000000;
const int sigma_size = 26;
struct Trie {
    int ch[maxnode][sigma_size];
    int val[maxnode];
    int sz;
    void clear( ) { sz = 1; memset ( ch[0], 0, sizeof ( ch[0] ) ); }
    int idx ( char c ) { return c - 'a'; }

    void Insert ( char *s, int v ) {
        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] = v;

    }
    bool find ( 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] ) return false;
			u = ch[u][c];
		}
        if ( val[u] ) return true;
        else return false;
	}
}trie;
int main ( ) {
   trie.clear ();
   int k=0,x,y,t;

    while(scanf("%s",w[k].word)!=EOF){
        trie.Insert ( w[k++].word, 1 );
    }

    for(int i=0;i<k;++i){
        t=strlen(w[i].word);

        for(int j=0;j<t;j++){
            char a[100]={},b[100]={};
            for(x=0;x<=j;++x)
                a[x]=w[i].word[x];
            for(y=0;x<t;++y,++x)
                b[y]=w[i].word[x];
           if(trie.find ( a )&&trie.find ( b )){
                printf("%s\n",w[i].word);
                break;
            }
        }
    }
    return 0;
}


 

<think>题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1247 题目要求:找出所有可以由字典中其他两个单词连接而成的单词。 分析提供的代码: 代码使用了字典树(Trie)来存储单词,并尝试在插入单词的过程中判断该单词是否可以由两个单词组成。 但代码中存在逻辑错误。 主要问题在`cr`函数(用于插入单词并同时检查): 1. 在插入过程中,每走一步,如果当前节点是一个单词的结尾(即`r[x]!=0`),则尝试判断剩余部分(s.substr(i+1))是否也是一个单词。 但是,这里存在两个问题: a. 在插入过程中,当前节点可能被标记为单词结尾(即之前插入的某个单词的结尾),但此时我们正在插入的单词还没有完成,所以这个检查时机可能不对。 b. 在同一个循环中,一旦遇到一个单词结尾,就会尝试检查剩余部分,但即使剩余部分不是单词,循环还会继续,而后面可能还会遇到单词结尾,然后再次检查。这样就会多次检查,但函数在第一次检查到满足条件时就应该返回,而不是继续检查(因为可能后面有多个单词结尾,但只需要有一个满足即可?)。然而,代码中并没有立即返回,而是继续循环,并且将最后一次检查的结果作为返回值。 2. 函数`cr`的返回值问题:函数返回`t`,而`t`是在循环中可能被多次赋值的,最终返回的是最后一次检查的结果。但是,我们只要在任意位置拆分满足条件(即前缀是一个单词,剩余部分也是一个单词)就应该返回true。所以,应该在第一次满足条件时就返回,或者至少记录下满足条件,然后继续循环直到结束?但是注意,一个单词可能有多种拆分方式,只要有一种拆分方式满足即可。 3. 另外,主函数中调用了两次`cr`函数:第一次在读取单词时调用`cr(s)`,但此时并没有使用返回值;第二次在遍历所有单词时,再次调用`cr(y[i])`,并使用其返回值来判断是否加入结果。这样会导致重复插入,而且第二次插入时,由于单词已经存在,`r[x]`会增加,并且拆分检查也会再次进行,但此时单词已经存在,所以拆分检查可能会因为单词本身在字典中而成功(即使拆分不正确,比如拆分成自己和一个空字符串?)但这样是不正确的。 4. 在`cr`函数中,当检查到当前节点是一个单词结尾(即`r[x]!=0`)时,才调用`find`检查剩余部分。但是,如果剩余部分为空(即已经到字符串末尾),那么`find`会检查空字符串,而空字符串不在字典中(因为题目中都是单词,没有空字符串),所以不会匹配。所以,拆分点不能在最后一个字符之后(即不能拆分成整个单词和空串)。这符合要求,因为要求由两个单词组成,所以拆分点必须在中间。 5. 但是,在第一次插入单词时,我们还没有构建完整的字典树,所以拆分检查可能失败(因为第二个单词可能还没有插入)。因此,通常的做法是:先构建完整的字典树,然后再逐个检查每个单词是否可以被拆分成两个单词。 因此,正确的步骤应该是: 步骤1:读取所有单词,构建完整的字典树。 步骤2:对于每个单词,尝试所有可能的拆分(将单词拆分成两个非空的部分),检查这两部分是否都在字典树中(注意:如果单词本身在字典树中,但拆分后可能是两个不同的单词)。 步骤3:如果存在一种拆分满足条件,则输出该单词。 而原代码试图在插入过程中就进行判断,但由于插入顺序是任意的,可能第二个单词还没有插入,所以无法正确判断。 修改建议: 1. 将构建字典树和检查分开:先读取所有单词,构建字典树(只插入,不检查)。 2. 然后,对于每个单词,在字典树中检查是否存在一种拆分方式,使得该单词可以被拆成两个单词(这两个单词都在字典树中)。 注意:在检查时,要避免将单词拆分成自身和空字符串,同时也要注意拆分点不能在开头(即第一个单词不能为空)。 因此,修改代码: 1. 去掉`cr`函数中的检查逻辑,只用于插入。 2. 另外写一个检查函数`check`,用于判断一个单词是否可以拆分成两个单词。 具体步骤: - 在`main`函数中,先读取所有单词,并插入字典树。 - 然后遍历每个单词,用`check`函数检查。 - 如果满足条件,则输出。 检查函数`check`的实现: 对于一个单词s,遍历拆分点i(从1到len-1),将单词拆分为s[0:i]和s[i:]。 检查s[0:i]是否是一个单词(在字典树中),并且s[i:]是否是一个单词(也在字典树中)。 只要找到一种拆分满足条件,就返回true。 注意:由于题目要求由两个单词组成,所以拆分点i的范围是[1, len-1](保证两个部分都非空)。 修改代码: 由于原代码中已经实现了`find`函数(用于检查一个单词是否在字典树中),我们可以直接使用。 但是,原`find`函数中,当`r[x]!=0`时才返回true,这表示一个单词的结尾。所以可以用。 修改后的代码结构: 1. 定义字典树结构(和原代码一样)。 2. 插入函数`insert`(原`cr`函数去掉检查部分,只插入)。 3. 查找函数`find`(不变)。 4. 检查函数`check`:对于给定的单词,尝试所有拆分,用`find`检查两部分。 注意:在插入时,我们只需要在单词的最后一个字符对应的节点将`r[x]++`,表示这个节点是一个单词的结尾(次数可以计数,但这里我们只需要标记存在,所以也可以使用bool数组,但题目没有说重复单词,所以按原代码用计数)。 另外,原代码中主函数读取单词后,又对每个单词调用`cr`(第二次调用),这是错误的,因为第二次调用会重复插入同一个单词,导致`r[x]`增加(可能变成2,3等),这样在`find`函数中,只要`r[x]!=0`就返回true,所以不影响查找,但重复插入没有意义,而且可能导致拆分检查错误(比如一个单词被拆成自己和一个空字符串,但空字符串不存在,所以不会错误,但重复插入会导致计数增加,而实际题目中单词是唯一的?题目没说,但输入是单词列表,可能有重复?但题目要求输出所有满足条件的单词,重复单词也要输出?但样例输入中没有重复,所以可以按重复单词处理,但重复单词插入多次会导致同一个单词被多次插入,在拆分检查时,如果这个单词由两个相同单词组成,那么拆分后两个单词都存在(因为插入了多次),所以可以检查到。但重复插入没有意义,而且浪费空间。所以建议在插入时如果已经存在,可以只增加计数,但为了简单,我们也可以不处理重复,因为题目没有明确说明。 根据题目,输入直到文件结束,所以我们可以按原方式读取。 修改后的主函数: 读取所有单词,插入字典树(每个单词插入一次)。 然后遍历所有单词,对每个单词用`check`函数检查,如果满足条件则输出。 注意:在检查一个单词时,不能拆分成自身(但自身已经插入到字典树中,所以拆分后的两个单词必须都是字典中的单词,且不能是当前单词本身?)但是,题目要求是“由字典中其他两个单词连接而成”,所以拆分后的两个单词必须都在字典中,而且可以是同一个单词(如果允许重复使用)?但题目没有说明,但样例中"a"和"ahat"都是独立的单词,所以拆分时两个单词都是字典中的独立单词。 例如:单词"aaaa"可以由"a"和"aaa"组成,如果"a"和"aaa"都在字典中,那么"aaaa"就满足条件。同样,也可以拆成"aa"和"aa",如果"aa"在字典中。 因此,在检查时,我们只需要检查拆分后的两个单词是否都在字典树中即可。 但是,注意:如果单词本身在字典树中出现多次(因为重复插入),那么计数可能大于1,但`find`函数只要大于0就返回true,所以不影响。 修改后的代码: 由于原代码要求不要大幅度改变逻辑及代码,所以我们尽量保留原有结构。 具体修改: 1. 将原来的`cr`函数重命名为`insert`,并去掉其中的检查逻辑(即去掉`t`和`find`的调用),只进行插入。 2. 增加一个`check`函数,用于检查一个单词是否可以拆分成两个单词(都在字典树中)。 3. 主函数中,先读取所有单词并插入,然后遍历每个单词,用`check`检查,满足则输出。 但是,原代码中主函数有两次插入(第一次读取时插入,第二次遍历时又插入),我们去掉第二次插入。 修改后的主函数: 读取所有单词,存入vector,并逐个插入字典树。 然后遍历vector,对每个单词调用`check`,如果返回true,则输出。 注意:原代码中,在读取单词时调用`cr(s)`,但此时`cr`函数还包含检查逻辑,所以我们需要修改`cr`函数(改为只插入)并改名。 另外,原代码中`cr`函数返回`t`(即检查结果),但第一次插入时,我们并不需要这个结果(因为此时字典树还不完整,检查结果不可靠),所以去掉返回值(或者改为void,但原代码在第二次调用时使用了返回值,所以我们需要调整)。 因此,我们修改为: - 插入函数`insert`:只插入,无返回值(或者返回void)。 - 检查函数`check`:返回bool。 然后主函数中,第一次读取时调用`insert`,第二次循环时调用`check`。 但是,原代码中第二次循环调用了`cr`,现在我们改为`check`。 修改代码: 由于原代码中`cr`函数在插入时还进行了检查,并且返回检查结果,而我们修改后的插入函数不再进行检查,所以我们需要将插入和检查分离。 因此,修改后的代码结构如下:</think>根据提供的代码和问题描述,我发现了代码中的几个关键错误,并进行了修正。以下是分析及解决方案: ### 错误分析 1. **插入逻辑错误**: - 在`cr`函数中,当遇到某个节点被标记为单词结尾时(`r[x]!=0`),会立即检查剩余部分是否构成单词 - 但此时整个单词尚未完全插入,字典树状态不完整 - 检查结果不可靠(可能第二个单词还未插入) 2. **重复插入问题**: - 主函数中对每个单词执行了两次插入操作 - 第一次在读取时`cr(s)` - 第二次在遍历时`cr(y[i])` - 导致字典树中单词计数错误 3. **检查时机错误**: - 在单词未完全插入时就尝试拆分检查 - 违反"先构建完整字典树再检查"的原则 ### 修正代码 ```cpp #include<bits/stdc++.h> using namespace std; const int N = 5e5 + 10; int shu[N][26]; int r[N], id = 0; // 查找单词是否存在 bool find(string s) { int x = 0, y; for (int i = 0; i < s.size(); i++) { y = s[i] - 'a'; if (shu[x][y] == 0) { return false; } x = shu[x][y]; } return r[x] > 0; // 确保是完整单词结尾 } // 仅插入单词,不进行检查 void insert(string s) { int x = 0, y; for (int i = 0; i < s.size(); i++) { y = s[i] - 'a'; if (shu[x][y] == 0) { shu[x][y] = ++id; } x = shu[x][y]; } r[x]++; // 标记单词结尾 } // 检查单词是否能拆分成两个单词 bool check(string s) { int x = 0, y; for (int i = 0; i < s.size() - 1; i++) { // 注意终止条件 y = s[i] - 'a'; x = shu[x][y]; // 当前缀是单词且剩余部分也是单词 if (r[x] > 0 && find(s.substr(i + 1))) { return true; } } return false; } int main() { vector<string> words, result; string s; // 第一阶段:构建完整字典树 while (cin >> s) { insert(s); words.push_back(s); } // 第二阶段:检查每个单词 for (auto &word : words) { if (check(word)) { result.push_back(word); } } // 输出结果 for (auto &res : result) { cout << res << endl; } return 0; } ``` ### 关键修正说明 1. **分离插入和检查逻辑**: - 将原`cr`函数拆分为: - `insert()`:仅负责插入单词 - `check()`:专门检查单词拆分 - 确保字典树完全构建后再进行检查 2. **修正检查函数**: - 遍历时终止条件改为`s.size()-1`(避免空子串) - 先验证前缀是完整单词(`r[x]>0`) - 再验证剩余部分是否在字典树中 3. **主流程优化**: - 第一阶段:读取所有单词并插入字典树 - 第二阶段:对每个单词执行拆分检查 - 避免重复插入,保证计数正确性 4. **边界处理**: - 在`check()`中确保拆分后两个部分都非空 - 在`find()`中严格检查完整单词(`r[x]>0`) ### 时间复杂度 - 插入:O(L) 每个单词,L为单词长度 - 检查:O(L^2) 每个单词(因substr操作) - 总复杂度:O(N*L^2),N为单词数量 ### 改进建议 1. 使用字符串引用避免拷贝 2. 预分配子串内存减少开销 3. 添加字典树重置逻辑(多测试用例场景)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值