zoukankan      html  css  js  c++  java
  • [SCOI2016]背单词

    背单词

    https://www.luogu.com.cn/problem/P3294

    前言:

    Trie树的省选题(瑟瑟发抖QAQ)

    问题汇总:(请忽略

    (1)对Trie字典树的运用不熟练

    (2)没想到可以将后缀转换为前缀来解决

    (3)对于并查集和vector不知道怎么加到这个程序中


    题目简述:

    PS:这道题话有点多,有点乱,建议借用草稿本理解题意

    给你n个互不相同的单词,学习这些单词要按照给定的规则吃泡椒,要求吃的泡椒数最少

    吃泡椒规则:(设当前单词为now,且1~now-1都已经记忆过)

    (1)如果存在一个单词是now的后缀且没有记忆过,那么学习now就要吃n×n颗泡椒

    (2)如果该单词的所有后缀都记忆过但都不在1~now-1的位置上,则要吃now颗泡椒

    (3)如果该单词的所有后缀记忆过且1~now-1中有now的后缀单词,设1~now-1中离
    now最近的为last,则只要吃now-last颗泡椒


    算法:

    贪心、Trie字典树、DFS深搜、并查集(并查集也许可以不用)


    简化规则+算法分析:

    1、简化规则:规则初看好复杂,但是仔细推敲一下,可以发现这个规则是可以简化的。

    (1)第一种需要n×n颗泡椒的情况,对于求最小值来说,肯定是要杜绝的。所以这种情况我们就直接忽略不计

    (2)其实原规则中的二三点有相似之处,差别就是now的后缀单词是否在now之前。原规则的第二点,其实可以看做last在0位置,则last-now=now,是符合题意的。所以,我们只需要考虑将now的后缀单词在now之前记忆,并使last尽量接近now

    综上,三大条规则就简化为一种贪心:将now的后缀单词在now之前记忆,并使last尽量接近now(重点!)

    2、算法分析(重点!!)

    (1)由简化规则,我们就可以得出要使用贪心(其实根据题意的求最小值也能得出)

    (2)后缀怎么处理呢?后缀数组(并不会)?其实转换一下思想,将字符串倒序一下,后缀不就变成了前缀吗?!那处理前缀怎么办?肯定优先想到Trie字典树呀!于是我们得出了第二个算法

    (3)建立了Trie树后,我们要遍历树并求距离(泡椒数),且要求距离和最小,这时,就有一个dfs序的东西。所以我们就用dfs序+子树从小到大排序来解决最小值问题

    PS:这道题关于dfs序的正确性证明,我也不是很懂,只能讲个大概:所有的size排序后,第i个根节点的代价是前面所有树的size之和,所以要dfs序。(且有时候并不只是dfs序可以解决最小值,其他序列也同样可以)

    (4)对于子树排序,还要用到STL容器中的vector,并查集也是辅助建立
    vector的


    代码理解:

    注意:

    (1)tot初始值为1,否则程序运行错误

    (2)每个数组的大小一定要开到510000以上(因为所有字符的长度总和 1<=|len|<=510000),否则会RE

    (3)ans一定要开long long,不然会爆int的内存,导致一个点以上WA掉

    #include <bits/stdc++.h>
    using namespace std;
    int n,tot=1,sum;
    long long ans;
    int ch[510001][26],fa[510001],son[510001],id[5100001],bo[5100001];
    char word[510001];
    vector<int> shan[510001];
    
    void insert(char *s,int now) { //建trie树 
    	int u=1,len=strlen(s);
    	for(register int i=len-1;i>=0;i--) {
    		int c=s[i]-'a';
    		if(!ch[u][c]) ch[u][c]=++tot;
    		u=ch[u][c];
    	}
    	bo[u]=now;
    }
    
    int find_fa(int x) { //并查集 
    	if(fa[x]==x) return fa[x];
    	else return fa[x]=find_fa(fa[x]);
    }
    
    void find_put(int x) { //不断遍历儿子,将每棵树装入vector中,方便排序 
    	for(int i=0;i<26;i++) {
            int net=ch[x][i];
            if(net!=0) {
                if(bo[net]==0) {
                    fa[net]=find_fa(x);
                }
                else  shan[bo[find_fa(x)]].push_back(bo[net]);
                find_put(net);
            }
        }
    }
    
    bool cmp(int x,int y) {
    	return son[x]<son[y];
    }
    
    void find_son(int x) { //找每个节点的儿子个数,然后根据树的大小进行排序 
    	son[x]=1;
    	for(vector<int>::iterator it=shan[x].begin();it!=shan[x].end();it++) {
    		int v=*it;
    		find_son(v);
    		son[x]+=son[v];
    	}
    	sort(shan[x].begin(),shan[x].end(),cmp);
    }
    
    void dfs(int x) { //dfs
    	id[x]=sum++; //该节点的深度 
    	for(vector<int>::iterator it=shan[x].begin();it!=shan[x].end();it++) {
    		int v=*it;
    		ans+=sum-id[x]; //累加吃泡椒数 
    		dfs(v);
    	}
    }
    
    int main() {
    	scanf("%d",&n);
    	for(register int i=1;i<=n;i++) {
    		scanf("%s",word);
    		insert(word,i);
    	}
    	for(register int i=1;i<=tot;i++) fa[i]=i;	
    	find_put(1);
    	find_son(0);
    	dfs(0);
    	printf("%lld",ans);
    	return 0;
    } 
    

    参考+基础:洛谷 Tian_Xing大佬的题解 (跪谢orz)


  • 相关阅读:
    2、什么是session?
    1、什么是cookie?
    Vuejs实战项目五:数据列表
    Vuejs实战项目四:权限校验
    vue开发中控制台报错问题
    Vuejs实战项目三:退出系统功能实现
    Vuejs实战项目:登陆页面
    Vuejs实战项目步骤一
    springmvc:文件上传
    ssm整合:搭建环境
  • 原文地址:https://www.cnblogs.com/Eleven-Qian-Shan/p/13050989.html
Copyright © 2011-2022 走看看