前缀单词 DP题解

写在前面

这道题是我以前做的,回顾这道题的时候觉得这道题好就写了一篇。

题目描述

一组单词是安全的,当且仅当不存在一个单词是另一个单词的前缀,这样才能保证数据不容易被误解。现在你手上有一个单词集合 S S ,你需要计算有多少个子集是安全的。

注意空集永远是安全的。

输入格式

第一行一个数 n ( n < = 50 ) n(n <= 50) ,表示集合的大小,以下 n n 行。每行一个由构成 a . . . . . . z 'a' ......'z' 的字符串。

输出格式

安全子集的个数。

样例

输入

3
hello
hell
hi

输出

6

分析

首先,我们需要定义一个 b o o l bool 数组 v i s [ i ] [ j ] vis[i][j] 来表示 j j 是否为 i i 的前缀。不妨将每个单词按字典序从小到大排序, F i n d b o l Findbol 函数更新 v i s vis 数组。
定义 d p [ i ] dp[i] 为 包含第 i i 个单词的子集总数,因为前面已经排了序,容易想出 d p [ i ] d p [ j ] dp[i] \geqslant dp[j]

代码

#include <cstdio>
#include <algorithm>
#include <climits>
#include <cmath>
#include <cstring>
#define LL long long
using namespace std;
const int MAXN = 55;
struct Node {
	char a[MAXN];
	int len;
}arr[MAXN];
bool cmp(Node x, Node y) {
	for(int i = 0; i < max(x.len, y.len); i ++) {
		if(x.a[i] > y.a[i]) return 0;
		else if(x.a[i] < y.a[i]) return 1;
	}
	return 0;
}
bool f[MAXN][MAXN];
bool Find_bol(int x, int y) {
	for(int i = 0; i < min(arr[x].len, arr[y].len); i ++) {
		if(arr[x].a[i] != arr[y].a[i]) return 0;
	}
	return 1;
}
LL dp[MAXN];
int main() {
	int n;
	LL sum = 0;
	scanf("%d", &n);
	for(int i = 1; i <= n; i ++) {
		scanf("%s", arr[i].a);
		arr[i].len = strlen(arr[i].a);
		dp[i] = 1;
	}
	sort(arr + 1, arr + 1 + n, cmp);
	for(int i = 1; i <= n; i ++) {
		for(int j = 1; j < i; j ++) {
			f[i][j] = Find_bol(i, j);
		}
	}
	for(int i = 1; i <= n; i ++) {
		for(int j = 1; j < i; j ++) {
			if(!f[i][j]) {
				dp[i] += dp[j];
			}
		}
	}
	for(int i = 1; i <= n; i ++) {
		sum += dp[i];
	}
	printf("%lld", sum + 1);
	return 0;
}

猜你喜欢

转载自blog.csdn.net/Clever_Hard/article/details/106861981