[Trie][LuoguP3294][SCOI2016]背单词

题意很迷,然后你可以发现一种情况就是为了告诉你这是贪心。

显然我们可以将单词倒序插入Trie树,然后按照子树内单词个数从小到大遍历,模拟统计答案。

然后我们就得到了一个优秀的40分代码。

#include <string.h>
#include <stdio.h>
#include <iostream>
#include <vector>
#include <algorithm>
#define LL long long
#define ULL unsigned long long
#define Uint unsigned int
using namespace std;

const int MAXN=510000+5;

int ch[MAXN][26],node,n,size[MAXN];
char s[MAXN];
bool ed[MAXN];

vector <int> nxt[MAXN];

inline void insert(char *x){
    int len=strlen(x+1),now=0;
    for(int i=len;i>=1;--i){
        int c=x[i]-'a';
        if(!ch[now][c]) ch[now][c]=++node;
        now=ch[now][c];
    }
    ed[now]=true;
}

inline void get_size(int now){
    size[now]=0;
    for(Uint i=0;i<nxt[now].size();++i){
        int v=nxt[now][i];
        get_size(v);
        size[now]+=size[v];
    }
    if(ed[now]) size[now]++;
}

LL ans=0;int tot=0;

bool cmp(const int &x,const int &y){
    return size[x]<size[y];
}

inline void get_ans(int now,int last){
    if(ed[now]) tot++,ans+=tot-last,last=tot;
    for(Uint i=0;i<nxt[now].size();++i){
        int v=nxt[now][i];
        get_ans(v,last);
    }
}

int main(){
    cin>>n;
    for(int i=1;i<=n;++i){
        scanf("%s",s+1);
        insert(s);
    }
    for(int i=0;i<=node;++i){
        for(int j=0;j<26;++j){
            if(ch[i][j]) nxt[i].push_back(ch[i][j]);
        }
    }
    get_size(0);
    for(int i=0;i<=node;++i) if(nxt[i].size())sort(nxt[i].begin(),nxt[i].end(),cmp);
    get_ans(0,0);
    cout<<ans;
    return 0;
}

为什么只有40分呢?

观察这样一组数据。(转自)

7
aa
aba
abb
ba
bb
bc
bd

我们可以得到这样一棵Trie树。

然后我们会先走右边再走左边。这样就会导致先走bba,再走aa,显然不优。

我们可以通过删去不要的节点来避免这种情况。

然后这就很优秀了。

事实上代码也就多一个rebuild函数。

#include <string.h>
#include <stdio.h>
#include <iostream>
#include <vector>
#include <algorithm>
#define LL long long
#define ULL unsigned long long
#define Uint unsigned int
using namespace std;

const int MAXN=510000+5;

int ch[MAXN][26],node,n,size[MAXN];
char s[MAXN];
bool ed[MAXN];

vector <int> nxt[MAXN];

inline void insert(char *x){
    int len=strlen(x+1),now=0;
    for(int i=len;i>=1;--i){
        int c=x[i]-'a';
        if(!ch[now][c]) ch[now][c]=++node;
        now=ch[now][c];
    }
    ed[now]=true;
}

int cnt;

inline void rebuild(int now,int last){
    if(ed[now]) nxt[last].push_back(++cnt),last=cnt;
    for(int i=0;i<26;++i){
        if(!ch[now][i]) continue;
        rebuild(ch[now][i],last);
    }
}

inline void get_size(int now){
    size[now]=1;
    for(Uint i=0;i<nxt[now].size();++i){
        int v=nxt[now][i];
        get_size(v);
        size[now]+=size[v];
    }
}

LL ans=0;int tot=0;

bool cmp(const int &x,const int &y){
    return size[x]<size[y];
}

inline void get_ans(int now,int last){
    if(now) tot++,ans+=tot-last,last=tot;
    for(Uint i=0;i<nxt[now].size();++i){
        int v=nxt[now][i];
        get_ans(v,last);
    }
}

int main(){
    cin>>n;
    for(int i=1;i<=n;++i){
        scanf("%s",s+1);
        insert(s);
    }
    rebuild(0,0);
    get_size(0);
    for(int i=0;i<=cnt;++i) if(nxt[i].size())sort(nxt[i].begin(),nxt[i].end(),cmp);
    get_ans(0,0);
    cout<<ans;
    return 0;
}

原文地址:https://www.cnblogs.com/jacktangs/p/9777314.html

时间: 2024-10-17 15:51:41

[Trie][LuoguP3294][SCOI2016]背单词的相关文章

【BZOJ4567】[Scoi2016]背单词 Trie树+贪心

[BZOJ4567][Scoi2016]背单词 Description Lweb 面对如山的英语单词,陷入了深深的沉思,“我怎么样才能快点学完,然后去玩三国杀呢?”.这时候睿智 的凤老师从远处飘来,他送给了 Lweb 一本计划册和一大缸泡椒,他的计划册是长这样的: ————— 序号  单词 ————— 1 2 …… n-2 n-1 n ————— 然后凤老师告诉 Lweb ,我知道你要学习的单词总共有 n 个,现在我们从上往下完成计划表,对于一个序号为 x 的单词(序号 1...x-1 都已经被

P3294 [SCOI2016]背单词

P3294 [SCOI2016]背单词 Trie+贪心 倒插进树+取出重建+子树处理+贪心遍历 倒插进树:把后缀转化为前缀,所以把字符串倒着插进Trie中 取出重建:重新建立一棵以单词为节点的树,如果存在包含(前缀)关系就连边 子树处理:处理出每棵树的大小用于贪心 贪心遍历:遍历整棵新树,累加答案 关于贪心:每次找到最小的子树统计答案 end. #include<iostream> #include<cstdio> #include<cstring> #include&

[bzoj4567][Scoi2016][背单词] (贪心+trie树)

Description Lweb 面对如山的英语单词,陷入了深深的沉思,“我怎么样才能快点学完,然后去玩三国杀呢?”.这时候睿智 的凤老师从远处飘来,他送给了 Lweb 一本计划册和一大缸泡椒,他的计划册是长这样的: ————— 序号  单词 ————— 1 2 …… n-2 n-1 n ————— 然后凤老师告诉 Lweb ,我知道你要学习的单词总共有 n 个,现在我们从上往下完成计划表,对于一个序号为 x 的单词(序号 1...x-1 都已经被填入): 1) 如果存在一个单词是它的后缀,并且

[SCOI2016] 背单词 (Trie树)

$pdf$ solution #include<iostream> #include<algorithm> #include<cstring> #include<cstdio> #include<vector> #define ll long long using namespace std; inline int read(){ int f=1,ans=0;char c=getchar(); while(c<'0'||c>'9'){

BZOJ4567: [Scoi2016]背单词

Description Lweb 面对如山的英语单词,陷入了深深的沉思,“我怎么样才能快点学完,然后去玩三国杀呢?”.这时候睿智 的凤老师从远处飘来,他送给了 Lweb 一本计划册和一大缸泡椒,他的计划册是长这样的: ————— 序号  单词 ————— 1 2 …… n-2 n-1 n ————— 然后凤老师告诉 Lweb ,我知道你要学习的单词总共有 n 个,现在我们从上往下完成计划表,对于一个序号为 x 的单词(序号 1...x-1 都已经被填入): 1) 如果存在一个单词是它的后缀,并且

bzoj 4567: [Scoi2016]背单词

Description Lweb 面对如山的英语单词,陷入了深深的沉思,"我怎么样才能快点学完,然后去玩三国杀呢?".这时候睿智 的凤老师从远处飘来,他送给了 Lweb 一本计划册和一大缸泡椒,他的计划册是长这样的: ----- 序号 单词 ----- 1 2 -- n-2 n-1 n ----- 然后凤老师告诉 Lweb ,我知道你要学习的单词总共有 n 个,现在我们从上往下完成计划表,对于一个序号为 x 的单词(序号 1...x-1 都已经被填入): 1) 如果存在一个单词是它的后

[SCOI2016]背单词

emm...可以允许我吐槽一下出题人的语文吗...这个题题面杀?! emm...毕竟是...细川银?! 这个题运用(百度)一些操作...我终于看懂了题面...QVQ 题目大意: 给你n个字符串,不同的排列有不同的代价,代价按照如下方式计算(字符串s的位置为x): 1.排在s后面的字符串有s的后缀,则代价为n^2: 2.排在s前面的字符串有s的后缀,且没有排在s后面的s的后缀 则代价为x-y(y为最后一个与s不相等的后缀的位置,注意:是位置): 3.s没有后缀,则代价为x. 求最小代价和. 这样的

【bzoj4567】SCOI2016背单词

题号莫名喜感. 倒序建Trie,dfs这棵Trie,贪心一下,每次按照size排序计算贡献就好. #include<bits/stdc++.h> #define N 100010 #define M 500010 using namespace std; typedef long long ll; ll ans=0; int ch[M][26],val[M],tpos[M],cnt,rt,tot,n,size[M]; char s[M]; vector<int> a[N]; inl

Luogu P3294 【[SCOI2016]背单词】

阅读理解题 ...... $Trie$ 后缀问题不好处理,我们把它转化为前缀问题,用字典树解决问题 贪心 容易想到,一个串的后缀要先于它插入 对于一个串和其若干后缀串,容易想到,我们要先插入后缀串 然后递归进入$size$最小的子串 bool cmp(const int &x,const int &y) { return size[x]<size[y]; } void makes(int x) { size[x]=1; for(int i=0;i<t[x].size();i++