[SCOI2016]背单词-题解

题意的话就看题面吧。

我们一步一步的来分析:

首先吃最少的泡椒,那么显然可以贪心,由于 n × n n\times n n×n贡献的肯定比后面的方式都大,所以我们考虑将一个串它的所有存在的后缀串全部先放在前面,这时就不会用第一种了,然后我们考虑,可以将这种关系用边连起来,就成了一棵树,我们可以举个例子来看:

5
a
b
ba
bb
bba

这个例子便可以连出这样的一张图:

lz

我们发现,如果先放 a a a这边的,那么最后就会是:

a - 1
ba - 1
bba - 1
b - 4
bb - 1

cost = 8

但是我们明显可以发现如果先放 b b b这边的,那么当到 a a a时,它只会有 3 3 3的代价,而其他都是 1 1 1的代价,所以如下这样会更优秀:

b - 1
bb - 1
a - 3
ba - 1
bba - 1

cost = 7

那么贪心的考虑,也就是这棵树上,我们按照子树大小,从小到大的遍历会更优秀一些。

所以一种方法是用Hash算法,在 O ( n 2 ) O(n^2) O(n2)的时间内建出这个树,然后遍历一遍排个序就可以出答案了。

目前瓶颈就在于建树,我们可以发现它的每个都是和后缀有关,但是直接建一棵广义后缀自动机太麻烦了,所以我们将其翻转后,后缀就变成了前缀,那么只需插入到一棵trie(字典)树中即可,对于一个串是另一个串的前缀,那么在字典树上它就是另一个的祖先(在另一个同一条链的上面),然后遍历一遍即可建出原树。

复杂度变成 O ( ∣ l e n ∣ ∗ 26 + n ) O(|len|*26+n) O(len26+n)就可以过了。

代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#define ll long long
using namespace std;
const int N=1e5+5,M=6e5+1;
int n,bef[N];
char str[M];
ll ans;
int son[M][26],f[N],flag[M],sze[N],bel[M],tot=1;
struct ss{
    int to,last;
    ss(){}
    ss(int a,int b):to(a),last(b){}
}g[M];
int head[N],cnt;
void add(int a,int b){g[++cnt]=ss(b,head[a]);head[a]=cnt;f[b]=a;}
void addin(char *s,int k){
    int id,now=1;
    int len=strlen(s);
    for(int i=len-1;i>=0;i--){
       id=s[i]-'a';
       if(!son[now][id]) son[now][id]=++tot;
       now=son[now][id];
    }
    bel[now]=k;//建trie树,记录是哪个字符串
}
struct node{
    int sze,id;
    node(){}
    node(int a,int b):sze(a),id(b){}
    bool operator <(node a)const{
		return sze<a.sze||(sze==a.sze&&id<a.id);
	}//按子树大小排序
};
vector <node> vec[N];
void dfs(int a,int fa){
    if(bel[a]) add(fa,bel[a]);
    //记录是从fa那个串来的
    for(int i=0;i<26;i++){
        if(son[a][i]) dfs(son[a][i],bel[a]?bel[a]:fa);
    }//建树
}
void find(int a){
    sze[a]=1;
    for(int i=head[a];i;i=g[i].last){
        find(g[i].to);
        vec[a].push_back(node(sze[g[i].to],g[i].to));
        sze[a]+=sze[g[i].to];
    }
    sort(vec[a].begin(),vec[a].end());//排序
}
int rak;
void get(int a){
    if(a) bef[a]=++rak;
    for(int i=0;i<vec[a].size();i++) get(vec[a][i].id);//遍历顺序,打上时间戳
}
void file(){
    freopen("word.in","r",stdin);
    freopen("word.out","w",stdout);
}
void close(){
    fclose(stdin);
    fclose(stdout);
}
int main(){
    file();
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%s",str);
        addin(str,i);
    }
    dfs(1,0);
    find(0);
    get(0);
    for(int i=1;i<=n;i++) ans+=1ll*(bef[i]-bef[f[i]]);//统计答案
    printf("%lld\n",ans);
    close();
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

VictoryCzt

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值