[USACO12DEC]First! G
[USACO12DEC]First! G
题目链接:https://www.luogu.com.cn/problem/P3065
看到字典序,想到的东西必然是 trie树 。我们将 \(n\) 个字符串全部插入 trie树 中,然后对于每一个字符串我们怎么检查是否能排在第一个呢。对于一个字符串,我们从头开始爬,想象一棵 trie树。如下图所示:
假设我们需要判断 ac 这个字符串能不能排在第一个。首先对于第0层,我们要走到 \(a\),所以在 0 的子树下面我们将 a 向其他在 0 子树下,同一层的节点(即 \(b,d\))连一条边,表示在我们设立的字典序中 \(a<b,a<d\) 。然后我们走到 \(a\) 这个节点,然后我们的目标是 \(c\),那么就让 \(c\) 对 \(a\) 得子树下,同一层的其他存在的字母(\(b,e\)) 连一条边。注意,这里不用对同是第二层的 \(a\) 连,因为它不在第一层 \(a\) 的子树当中,在我们设定 \(a<b,a<d\) 时,它所在字符串的字典序就已经比 ac 这个字符串要大了。最后我们走到 \(c\)。然后停止。
我们得到一个图,这个图描绘了大小关系,如果这个图的大小关系成立的话,那么这个字符串就能够通过改变字母表的顺序来让它的字典序最小。如何判断这个图的大小关系是否成立呢?我们只需判断这个图中是否有环就够了。有环,则不成立。那么为什么满足这幅图的大小关系就行了呢,毕竟这幅图描绘的大小关系并不完整。因为通过我们在 trie树 上的操作,如果这幅图的大小关系满足了,这个字符串是一定能排到第一位的。对于其他的每个字符串,字典序的大小肯定是它们从左往右第一个不同的字符进行比较得到的。从左往右的第几位,相当于 trie树 中第几层,而我们的关系使得当前字符串的每一个字符在它所对应的那层中都是第一小的。这幅图描绘的大小关系不完整也没关系,因为我们只要判断这个字符串能不能排到第一小,至于其他的大小关系,构造一种合法情况就好了,比如把后面一串没排的接在已知最大的后面。
还有一个小细节,就是如果 \(n\) 个字符串中存在一个字符串的前缀,那么这个字符串肯定不能排到第一个。
总时间复杂度 \(O(26\times m)\)。(\(m\) 表示字符总数)
代码如下:
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 3e5+5;
string s[30005];
int n;
struct Trie
{
int tot,to[MAXN][27],exist[MAXN],in[30];
vector <int> e[27];
void init()
{
memset(in,0,sizeof in);
for(int i=0;i<=26;++i) e[i].clear();
}
void insert(string s)
{
int now=0;
for(int i=0;i<s.size();++i)
{
int num=s[i]-'a';
if(to[now][num]==0) to[now][num]=++tot;
now=to[now][num];
}
exist[now]=1;
}
bool check(string s)
{
int now=0;
for(int i=0;i<s.size();++i)
{
int num=s[i]-'a';
if(exist[now]) return false;
for(int j=0;j<26;++j)
if(to[now][j]!=0&&j!=num)
e[num].push_back(j),in[j]++;
now=to[now][num];
}
queue <int> q;
for(int i=0;i<26;++i) if(!in[i]) q.push(i);
while(!q.empty())
{
int p=q.front();
q.pop();
for(int i=0;i<e[p].size();++i)
{
int to=e[p][i];
in[to]--;
if(!in[to]) q.push(to);
}
}
for(int i=0;i<26;++i) if(in[i]) return false;
return true;
}
}t;
bool ans[30005];
int main()
{
std::ios::sync_with_stdio(false);
cin>>n;
for(int i=1;i<=n;++i)
{
cin>>s[i];
t.insert(s[i]);
}
int cnt=0;
for(int i=1;i<=n;++i)
{
t.init();
if(t.check(s[i])) ans[i]=1,cnt++;
}
cout<<cnt<<"\n";
for(int i=1;i<=n;++i)
if(ans[i]) cout<<s[i]<<"\n";
return 0;
}
原文地址:https://www.cnblogs.com/nightsky05/p/15003988.html
- 【开源】QuickPager ASP.NET2.0分页控件V2.0.0.6 修改了几个小bug,使用演示。
- Invoke-PSImage:将PS脚本隐藏进PNG像素中并用一行指令去执行它
- linux的内存清理相关知识!
- 一个页面搞定几乎所有的列表需求的实现思路和一点代码。
- 实现 Table 的行交替颜色、选中行变色的一种方法。演示+源码
- 【知识】使用Python来学习数据科学的完整教程
- 【开源】QuickPager ASP.NET2.0分页控件V2.0.0.4 增加了几个分页算法
- 虚拟时钟
- 正弦函数仿真
- 《你必须知道的.net》读书笔记 004 —— 1.4 多态的艺术
- 桶形移位寄存器(二)
- ROM
- 【查询】查询好像也可以很简单!
- XDC
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 《剑指offer》第六天:重建二叉树
- Android | 《看完不忘系列》之Glide
- Android | 资源冲突覆盖的一些思考
- 如何获取流式应用程序中checkpoint的最新offset
- Spark之离线统计热点城市信息
- 使用OpenCV实现图像增强
- typescript基础篇(4):函数
- 这样的奇技淫巧,劝你不用也罢
- 一文详解设备ID的那些事儿
- Ansible搭建hadoop-3.1.3高可用
- Android | xml和view的那些事
- Android | Glide细枝篇
- 从源代码编译安装 MonoDevelop 记录
- 在 Asp.Net Core WebAPI 中防御跨站请求伪造攻击
- Hash 算法有哪些?