[SCOI 2016] 背单词(Trie 树 + 贪心) | 错题本

题目

[SCOI 2016] 背单词

题意

什么阴间题目描述。
给出 n n n 个字符串,你需要将他们排成适当的序列使序列中各个串的权值和最小。
序列中的一个字符串 s i   ( 1 ≤ i ≤ n ) s_i\ (1 \le i \le n) si (1in) 权值定义如下:

  • 若存在 s j   ( j > i ) s_j\ (j > i) sj (j>i) s i s_i si 的后缀,权值为 n 2 n^2 n2
  • 否则,若任意 s j   ( j < i ) s_j\ (j < i) sj (j<i) 都不是 s i s_i si 的后缀,权值为 i i i
  • 否则,设 j   ( j < i ) j\ (j < i) j (j<i) 是满足 s j s_j sj s i s_i si 后缀的最大的下标,权值为 i − j i - j ij

分析

由题意可得一个串 s s s 的后缀必然不能出现在 s s s 的后面,否则代价高于一切。那么将所有 s s s 倒序插入 Trie 中,删除无意义节点后,每个节点视为以它开头的那个串。我们需要设计一个节点序列,使各个节点与它的祖先节点在序列中的最小距离之和最小(当然,祖先必须在该节点前出现),显然需要按 DFS 序,因为节点 u u u 出现过后,如果 u u u 有儿子,下一个出现的必定是 u u u 的某个儿子 v v v,因为这样可以保证 v v v 的代价是 1 1 1,且能够递归下去。再 u u u 过后考虑选哪个儿子 v v v,由于遍历完 v v v 子树过后,对于其他儿子,每个的代价都会增加 v v v 的子树大小,因此子树大小越小的必然越要靠前。于是按子树大小从小到大 DFS 计算代价即可。

代码

#include <bits/stdc++.h>

int Read() {
	int x = 0; char c = getchar();
	while (c < '0' || c > '9')
		c = getchar();
	while (c >= '0' && c <= '9')
		x = x * 10 + (c ^ 48), c = getchar();
	return x;
}

const int MAXN = 510000;
const int MAXL = 510000;

int N;
char S[MAXL + 5];
int Par[MAXN + 5];

int Find(const int &u) {
	return (u == Par[u]) ? u : (Par[u] = Find(Par[u]));
}

int Num[MAXN + 5];
int T[MAXN + 5][30], Cnt;

void Insert(char *str, int len, int id) {
	int u = 0;
	for (int i = len; i >= 1; i--) {
		int x = str[i] - 'a';
		if (!T[u][x])
			T[u][x] = ++Cnt;
		u = T[u][x];
	}
	Num[u] = id;
}

std::vector<int> G[MAXN + 5];

void Build(const int &u, const int &p) {
	if (Num[u])
		G[p].push_back(Num[u]);
	for (int i = 0; i < 26; i++)
		if (T[u][i])
			Build(T[u][i], Num[u] ? Num[u] : p);
}

int Siz[MAXN + 5];

bool Comp(const int &i, const int &j) {
	return Siz[i] < Siz[j];
}

void Init(const int &u) {
	Siz[u] = 1;
	for (int v: G[u])
		Init(v), Siz[u] += Siz[v];
	std::sort(G[u].begin(), G[u].end(), Comp);
}

int Dfn;
long long Ans;

void Dfs(const int &u) {
	int pos = Dfn++;
	for (int v: G[u]) {
		Ans += Dfn - pos;
		Dfs(v);
	}
}

int main() {
	N = Read();
	for (int i = 1; i <= N; i++) {
		scanf("%s", S + 1);
		Insert(S, strlen(S + 1), i);
	}
	for (int i = 1; i <= Cnt; i++)
		Par[i] = i;
	Build(0, 0), Init(0), Dfs(0);
	printf("%lld", Ans);
	return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值