字符串的问题
传送门(KMP)
void solve()
{
int n, m, k;
string s;
cin >> s;
s = " " + s;
int len = s.size();
vector<int> nxt(len+1);
nxt[1] = 0;
for (int i=2; i<len; ++i){
nxt[i] = nxt[i-1];
while (nxt[i] && s[i]!= s[nxt[i]+1]) nxt[i] = nxt[nxt[i]];
nxt[i] += (s[i] == s[nxt[i]+1]);
}
vector<int> res(s.size()+1, 0);
for (int i=0; i<s.size()-1; ++i){
res[nxt[i]]++;
}
int ans = nxt[s.size()-1];
if (ans <= 0){
cout << "Just a legend\n";
}
else if (res[ans]){
cout << s.substr(1, ans) << endl;
}
else if (nxt[ans]){
cout << s.substr(1, nxt[ans]) << endl;
}
else{
cout << "Just a legend\n";
}
}
KMP字符串匹配
void solve()
{
int n, m, k;
string s1, s2;
cin >> s1 >> s2;
s1 = " " + s1, s2 = " " + s2;
int len = s2.size();
vector<int> nxt(len+1, 0);
for (int i=2; i<len; ++i){
nxt[i] = nxt[i-1];
while (nxt[i] > 0 && s2[i]!= s2[nxt[i]+1]) nxt[i] = nxt[nxt[i]];
nxt[i] += (s2[i] == s2[nxt[i]+1]);
}
int len2 = s1.size();
vector<int> ans;
for (int i=1, j=0; i<len2; ++i){
while (j && s1[i]!= s2[j+1]) j = nxt[j];
j += (s1[i] == s2[j+1]);
if (j == len-1){
ans.pb(i-len+2);
j = nxt[j];
}
}
if (!ans.empty()){
for (auto x:ans){
cout << x << endl;
}
}
for (int i = 1; i<len; i++){
cout << nxt[i] << " \n"[i == len-1];
}
}
struct KMP{
vector<int> nxt;
vector<int> t;
int len;
string s;
void clear(){
len = 0;
nxt.clear();
t.clear();
}
// s = " " + s;
KMP(string ss) : s(ss), len(ss.size()) {
t.resize(len+1);
nxt.resize(len+1);
nxt[1] = 0;
for (int i=2;i<len;i++){ // nxt数组初始化
nxt[i] = nxt[i-1];
while (nxt[i]&&ss[i]!=ss[nxt[i]+1]) nxt[i] = nxt[nxt[i]];
nxt[i]+=(ss[i]==ss[nxt[i]+1]); // 找到匹配的加1
}
}
vector<int> match(string ss){
int len_s = ss.size();
vector<int> start_pos;
for (int i=1,j=0;i<len_s;i++){
while (j && ss[i] != s[j+1])j = nxt[j];
j += (ss[i] == s[j+1]);
if (j == len-1){
start_pos.push_back(i-len+2);
j = nxt[j];
}
}
return start_pos;
}
};
void solve()
{
int n, m, k;
string s1, s2;
cin >> s1 >> s2;
s1 = " " + s1, s2 = " " + s2;
KMP kmper(s2);
auto ans = kmper.match(s1);
if (!ans.empty()){
for (auto x:ans){
cout << x << endl;
}
}
for (int i = 1; i<kmper.len; i++){
cout << kmper.nxt[i] << " \n"[i == kmper.len-1];
}
}
栗酱的数列
传送门(KMP)
int n, m, k;
template <typename T>
struct KMP {
// 时间复杂度O(|S|+|T|)
std::vector<int> nxt;
std::vector<T> t; // 使用模板参数T
int len;
std::vector<T> s; // 使用模板参数T
void clear() {
len = 0;
nxt.clear();
t.clear();
}
// s = " " + s;
KMP(std::vector<T> ss) : s(ss), len(ss.size()) {
t.resize(len+1);
nxt.resize(len+1);
nxt[1] = 0;
for (int i=2;i<len;i++){ // nxt数组初始化
nxt[i] = nxt[i-1];
while (nxt[i]&&s[i]!=s[nxt[i]+1]) nxt[i] = nxt[nxt[i]];
nxt[i]+=(s[i]==s[nxt[i]+1]); // 找到匹配的加1
}
}
// t = " " + t;
std::vector<int> match(std::vector<T> t){ // 字符串匹配
int len_s = t.size();
std::vector<int> start_pos;
for (int i=1,j=0;i<len_s;i++){
while (j && (t[i] + s[j+1])%k != 0) j = nxt[j];
j += ((t[i] + s[j+1])%k == 0);
if (j == len-1){
start_pos.push_back(i-len+2);
j = nxt[j];
}
}
return start_pos;
}
/* 循环周期 形如 abcab 中 abc 是一个合法周期 */
std::vector<int> periodic(){
std::vector<int> ret;
int now = len-1;
while (now){
now = nxt[now];
ret.push_back(len-1-now); // 会加一个本身,也是其一个周期
}
return ret;
}
/* 循环节 形如 acac 中ac、acac是循环节,aca不是*/
std::vector<int> periodic_loop(){
std::vector<int>ret ;
for (int x :periodic()){
if ((len-1)%x==0)ret.push_back(x); // 没有本身
}
return ret;
}
int min_periodic_loop(){ // 最小循环节
return periodic_loop()[0];
}
};
void solve()
{
cin >> n >> m >> k;
vector<int> a(n+1), b(m+1);
for (int i = 1; i<=n; i++) cin >> a[i];
for (int i = 1; i<=m; i++) cin >> b[i];
vector<int> diff_a(n), diff_b(m);
for (int i = 1; i<n; i++) {
diff_a[i] = ((a[i]-a[i+1])%k+k)%k;
// cout << diff_a[i] << " \n"[i==n-1];
}
for (int i = 1; i<m; i++) {
diff_b[i] = ((b[i]-b[i+1])%k+k)%k;
// cout << diff_b[i] << " \n"[i==m-1];
}
KMP<int> kmper(diff_b);
vector<int> ans = kmper.nxt;
// for (int i = 1; i<=n-1; i++){
// cout << ans[i] << " ";
// }
cout << kmper.match(diff_a).size() << endl;
kmper.clear();
}
K匹配
传送门(KMP)
感觉题意描述的不是很好,很容易理解为是S里的子串中有多少个T里的子串,
但其实是S里的子串有多少个包含T这个串,这样我们每遇到一个符合的S子串就看前面有多少能和该结合成为S的子串,最后做个累加。
比如这里的last就为x-m+1个。
template <typename T>
struct KMP {
// 时间复杂度O(|S|+|T|)
vector<int> nxt;
vector<T> t; // 使用模板参数T
int len;
vector<T> s; // 使用模板参数T
void clear() {
len = 0;
nxt.clear();
t.clear();
}
// s = " " + s;
KMP(vector<T> ss) : s(ss), len(ss.size()) {
t.resize(len+1);
nxt.resize(len+1);
nxt[1] = 0;
for (int i=2;i<len;i++){ // nxt数组初始化
nxt[i] = nxt[i-1];
while (nxt[i]&&s[i]!=s[nxt[i]+1]) nxt[i] = nxt[nxt[i]];
nxt[i]+=(s[i]==s[nxt[i]+1]); // 找到匹配的加1
}
}
// t = " " + t;
int match(vector<T> t){ // 字符串匹配
int len_s = t.size();
vector<int> f(len_s, 0);
int last = 0;
// vector<int> start_pos;
for (int i=1,j=0;i<len_s;i++){
while (j && t[i] != s[j+1]) j = nxt[j];
j += (t[i] == s[j+1]);
if (j == len-1){
// start_pos.push_back(i-len+2);
j = nxt[j];
last = i-len+2;
}
f[i] = f[i-1]+last;
}
return f[len_s-1];
}
};
void solve()
{
int n, m, k;
cin >> n >> k;
string s1, s2;
cin >> s1 >> s2;
s1 = " " + s1;
s2 = " " + s2;
vector<char> traslate_s2(s2.begin(), s2.end()), traslate_s1(s1.begin(), s1.end());
KMP<char> kmper(traslate_s2);
cout << kmper.match(traslate_s1) << endl;
}
数一数
传送门(KMP)
这题也是比比较搞得,这里乘法得话我们就可以省略很多步骤,因为当这n个字符串的长度不相等时,f(si,sj)肯定为0,所以我们只要考虑最小的长度的字符串有几个匹配的,并且它这次匹配结果与该长度的一样,所以只用计算一次,当遇到长度也为最小时,直接输出结果即可,其他长度直接为0。感觉也可以直接hash看最小长度的字符串是否相等就可。
template <typename T>
struct KMP {
// 时间复杂度O(|S|+|T|)
vector<int> nxt;
vector<T> t; // 使用模板参数T
int len;
vector<T> s; // 使用模板参数T
void clear() {
len = 0;
nxt.clear();
t.clear();
}
// s = " " + s;
KMP(vector<T> ss) : s(ss), len(ss.size()) {
t.resize(len+1);
nxt.resize(len+1);
nxt[1] = 0;
for (int i=2;i<len;i++){ // nxt数组初始化
nxt[i] = nxt[i-1];
while (nxt[i]&&s[i]!=s[nxt[i]+1]) nxt[i] = nxt[nxt[i]];
nxt[i]+=(s[i]==s[nxt[i]+1]); // 找到匹配的加1
}
}
// t = " " + t;
vector<int> match(vector<T> t){ // 字符串匹配
int len_s = t.size();
vector<int> start_pos;
for (int i=1,j=0;i<len_s;i++){
while (j && t[i] != s[j+1]) j = nxt[j];
j += (t[i] == s[j+1]);
if (j == len-1){
start_pos.push_back(i-len+2);
j = nxt[j];
}
}
return start_pos;
}
};
void solve()
{
int n;
cin >> n;
vector<string> s(n);
int mn = 1e9;
for (int i = 0; i<n; i++){
cin >> s[i];
s[i] = " "+s[i];
if (s[i].size() < mn){
mn = s[i].size();
}
}
int g = -1;
for (int i = 0; i<n; i++){
if (s[i].size() == mn){
if (g == -1){
int res = 1;
for (int j = 0; j<n; j++){
if (i == j) continue;
vector<char> traslate_i(s[i].begin(), s[i].end()), traslate_j(s[j].begin(), s[j].end());
KMP<char> kmp(traslate_i);
res = res*kmp.match(traslate_j).size()%mod;
}
cout << res << endl;
g = res;
}
else cout << g << endl;
}
else cout << 0 << endl;
}
}
假的字符串
传送门(字典树)
给定n个字符串,互不相等,你可以任意指定字符之间的大小关系(即重定义字典序),求有多少个串可能成为字典序最小的串,并输出它们
思路:这题的题面也是不好理解,简单就是这n个字符串都存在,且会相互影响,对于这n个分别判断其中一个能否是最小的字典序,
所以首先我们先将它们存入字典树中,再分别判断其能否成为,并且考虑字典的顺序,将在一个字母后的建边,并使用拓扑排序看是否有环,并在途中检查它的路径是否有完整的字符串,因为一个它的前缀的字典序必定比它小。
这道题会爆longlong,小心。。。
struct Trie {
#define f(x, y) t[x].ch[y]
// 以该点为结尾的字符串个数cnt、是否是完整字符串的标志ok,子节点的数组ch。
struct Node {
int cnt;
bool ok;
array<int,26> ch;
Node(): cnt(0), ch{}, ok(false) {}
};
vector<Node> t;
Trie() {
init();
}
void init() {
newNode();
}
// newNode()函数用于创建一个新的节点,并将其添加到字典树中,返回该节点的索引。
int newNode() {
t.push_back(Node());
return t.size() - 1;
}
// 将字符转换为在字典树中的索引
int get(char c) {
return c - 'a';
}
// 插入函数
// 同时更新每个节点的字符计数cnt和是否为完整字符串的标志ok
void insert(const string &s) {
int n = s.size() - 1;
int p = 0;
for (int i = 1; i <= n; i++) {
int u = get(s[i]);
if (f(p, u) == 0) {
int k = newNode();
f(p, u) = k;
}
p = f(p, u);
}
t[p].cnt++;
t[p].ok = true;
}
// 查询函数,返回节点的cnt、ok、ch。
// 返回该字符串最后一个字符所在节点的信息
Node query(const string &s) {
int n = s.size() - 1;
int p = 0;
for (int i = 1; i <= n; i++) {
int u = get(s[i]);
if (f(p, u) == 0) return Node(); // 不存在返回一个新节点
p = f(p, u);
}
return t[p];
}
bool check(const string &s){
int n = s.size() - 1;
int p = 0;
array<vector<int>, 26> g{};
array<int, 26> din{};
for (int i = 1; i<=n; i++){
int u = get(s[i]);
for (int j = 0; j<26; j++){
if (j == u || f(p, j)==0) continue;
g[u].push_back(j);
din[j]++;
}
p = f(p, u);
if (t[p].ok && i != n) return false; // 防止是前缀
}
queue<int> q;
for (int i = 0; i<26; i++){
if (din[i] == 0) q.push(i);
}
while (!q.empty()){
int u = q.front();
q.pop();
for (int v : g[u]){
if (--din[v] == 0){
q.push(v);
}
}
}
bool ok = true;
for (int i = 0; i<26; i++){
ok &= (din[i] == 0);
}
return ok;
}
#undef f
};
void solve()
{
int n, m, k;
cin >> n;
vector<string> s(n+1);
Trie trie;
for (int i = 1; i<=n; i++){
cin >> s[i];
s[i] = " "+s[i];
trie.insert(s[i]);
}
vector<string> ans;
for (int i = 1; i<=n; i++){
if (trie.check(s[i])){
ans.push_back(s[i]);
}
}
cout << ans.size() << endl;
for (int i = 0; i<ans.size(); i++){
cout << ans[i].substr(1, ans[i].size()-1) << endl;
}
}
奶牛异或
传送门(字典树)
思路:首先求出异或前缀和,每个区间异或和可以表示成两个前缀和的异或,问题转换成n+1个数字重心选两个做异或运算的最大值,可以用01Trie解决。相同宽度的两个二进制数字的大小关系等价于字典序关系。枚举一个数字x,然后寻找与它异或结果最大的另一个数字y。从高到低考虑x的每一个二进制位bit,如果y的这一位也是bit,则异或结果为0,反之为1。所以在01Trie上寻找y:从跟出发,有 !bit 边,则走 !bit 边,否则只能走bit边。这里将叶子节点来记录位置,便于了后于访问。并且注意要先建0的分支,构建一条全是0的分支,表示从第一个开始异或。
struct Trie {
#define f(x, y) t[x].ch[y]
struct Node {
array<int,2> ch;
Node(): ch{} {}
};
vector<Node> t;
vector<int> pos;
Trie() {
init();
}
void init() {
newNode();
}
// newNode()函数用于创建一个新的节点,并将其添加到字典树中,返回该节点的索引。
int newNode() {
t.push_back(Node());
pos.push_back(0);
return t.size() - 1;
}
// get()函数用于获取x的第y位的值,0或1。
int get(int x, int y) {
return (x>>y)&1;
}
// 插入函数
void insert(const int &x, const int &location) {
// cout << "insert " << x << " " << location << endl;
int p = 0;
for (int i = 20; i>=0; i--){
int v = get(x, i);
if (f(p, v) == 0) f(p, v) = newNode();
p = f(p, v);
}
pos[p] = location; //给叶节点赋值为位置,便于后续访问
}
// 查询函数,返回最大异或值和该位置的位置
pair<int, int> query(const int &x) {
int p = 0;
int mx = 0;
for (int i = 20; i>=0; i--){
int v = get(x, i);
if (f(p, !v)){
p = f(p, !v);
mx |= (1<<i);
}
else {
p = f(p, v);
}
}
return make_pair(mx, pos[p]);
}
#undef f
};
void solve()
{
int n, m, k;
cin >> n;
Trie trie;
vector<int> a(n+1), sum_xor(n+1);
int ans = -1, L = 0, R = n;
trie.insert(0, 1); // 这部步很重要,因为构建一条全是0的分支,表示从第一个开始异或
for (int i = 1; i<=n; i++){
cin >> a[i];
sum_xor[i] = sum_xor[i-1] ^ a[i];
auto [XOR, p] = trie.query(sum_xor[i]);
if (XOR > ans){
ans = XOR;
L = p, R = i;
}
trie.insert(sum_xor[i], i+1);
}
cout << ans << " " << L << " " << R << endl;
}