题目:

洛谷1117

分析:

定义把我校某兔姓神犇Tzz和他的妹子拆分,为“优秀的拆分”

随便写个哈希就能有\(95\)分的好成绩……

我的\(95\)分做法比fei较chang奇葩,不想浪费时间的可以忽略解法一qwq

解法一:

用\(n\)个vector记录对于每个点\(i\),哪些长度\(len\)满足\(i+2len\leq n\)且\(str[i, i+len)=str[i+len,i+2len)\)(即形如“\(AA\)”)。然后,枚举开头\(l\)和“\(AA\)”的长度\(len\),这种情况下的的答案就是以\(l+2len\)开头的“\(AA\)”数量(即“\(BB\)”)。

解法二:

这题哈希可以过的说qwq(虽然我惨遭卡常没过去)

预处理出以每个点开始和结尾的“\(AA\)”的数量\(st[i]\)和\(ed[i]\),那么答案就是\(\sum\limits_{i=1}^{len-1}ed[i-1]st[i]\)。

暴力枚举一个\(A\)的长度\(len\),然后每隔\(len\)个点作一个标记,共\(\lceil \frac {n}{len} \rceil\)个。可以发现任意长为\(2len\)的“\(AA\)”都会经过两个标记。枚举标记,计算相邻两标记\(a\)和\(b\)开头的后缀的\(LCP\)(最长公共前缀)和两标记结尾的前缀的\(LCS\)(最长公共后缀)。如果\(LCP+LCS\geq len\)则存在一个“\(AA\)”串经过这两个标记,且这些串的起始位置是连续的。简单画个图就可以发现,它们的起始位置是\([a-LCS+1,a+LCP-len]\)。(脑补一下,第一个\(A\)的开头不能比\(a-LCS+1\)小,第一个\(A\)的结尾不能比\(a+LCP-1\)大。)用差分解决区间修改。

求\(LCP\)和\(LCS\)可以二分+哈希解决。根据某些神奇的原理,\(O(\sum\limits _{i=1}^n \frac{n}{i})=O(n\log n)\)(貌似叫调和级数)。里面再套个二分,复杂度\(O(n\log^2n)\)。

解法三:

用后缀数组+ST表\(O(1)\)查询\(LCP\)和\(LCS\),复杂度\(O(n\log n)\)。(什么,你不会后缀数组/不会用后缀数组查\(LCP\)和\(LCS\)?戳我:【知识总结】后缀数组(Suffix_Array)

代码:

一、奇葩的哈希\(95\)分做法

五个月前写的代码,比较奇葩……

#include <iostream>
#include <cstring>
#include <algorithm>
#include <string>
#include <vector>
using namespace std;
namespace zyt
{
typedef long long ll;
typedef pair<ll, ll> pll;
const int N = 2010, seed = 131, p[2] = {(int)1e9 + 7, (int)1e9 + 9};
ll F[N][2];
ll h[N][2];
void init()
{
F[0][0] = F[0][1] = 1;
for (int i = 1; i < N; i++)
{
F[i][0] = F[i - 1][0] * seed % p[0];
F[i][1] = F[i - 1][1] * seed % p[1];
}
}
void init(const string &str)
{
h[0][0] = h[0][1] = str[0] - 'a';
for (int t = 0; t < 2; t++)
for (int i = 1; i < str.size(); i++)
h[i][t] = (h[i - 1][t] * seed + str[i] - 'a') % p[t];
}
pll Hash(const string &str)
{
ll ans[2] = {0, 0};
for (int t = 0; t < 2; t++)
for (int i = 0; i < str.size(); i++)
ans[t] = (ans[t] * seed + str[i] - 'a') % p[t];
return make_pair(ans[0], ans[1]);
}
pll Hash(const int l, const int r)
{
if (l == 0)
return make_pair(h[r][0], h[r][1]);
else
{
int len = r - l + 1;
return make_pair(
(h[r][0] - h[l - 1][0] * F[len][0] % p[0] + p[0]) % p[0],
(h[r][1] - h[l - 1][1] * F[len][1] % p[1] + p[1]) % p[1]);
}
}
vector<int>repeat[N];
void work()
{
ios::sync_with_stdio(false);
int T;
init();
cin >> T;
while (T--)
{
int ans = 0;
string str;
cin >> str;
init(str);
for (int l = 0; l < str.size(); l++)
{
vector<int>().swap(repeat[l]);
for (int r = l; r < str.size(); r++)
{
int len = r - l + 1;
if (r + len < str.size() && Hash(l, r) == Hash(r + 1, r + len))
repeat[l].push_back(len * 2);
}
}
for (int l = 0; l < str.size(); l++)
for (int i = 0; i < repeat[l].size(); i++)
{
int r = l + repeat[l][i];
if (r < str.size())
ans += repeat[r].size();
}
cout << ans << endl;
}
}
}
int main()
{
zyt::work();
return 0;
}

二、\(O(n\log^2 n)\)的哈希期望\(100\)实际\(95\)的做法

(我是哪里写挂了还是常数太大啊qwq,\(O(n\log ^2n)\)凭什么过不了\(30000\)啊,我周围一圈神仙都能过的qwq

#include <iostream>
#include <cstring>
#include <algorithm>
#include <string>
using namespace std;
namespace zyt
{
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
const int N = 3e4 + 10;
namespace Hash
{
typedef pii hash_t;
const hash_t seed = pii(29, 29), p = pii(1e9 + 7, 1e9 + 9);
pii operator + (const pii &a, const pii &b)
{
return make_pair(a.first + b.first, a.second + b.second);
}
pii operator - (const pii &a, const pii &b)
{
return make_pair(a.first - b.first, a.second - b.second);
}
pll operator * (const pii &a, const pii &b)
{
return make_pair((ll)a.first * b.first, (ll)a.second * b.second);
}
pii operator % (const pll &a, const pii &p)
{
return make_pair(a.first % p.first, a.second % p.second);
}
hash_t h[N], pow[N];
inline void init()
{
pow[0] = make_pair(1, 1);
for (int i = 1; i < N; i++)
pow[i] = pow[i - 1] * seed % p;
}
inline int ctoi(const char c)
{
return c - 'a';
}
inline hash_t ctoh(const char c)
{
return make_pair(ctoi(c), ctoi(c));
}
inline void get_hash(const string &s)
{
h[0] = ctoh(s[0]);
for (int i = 1; i < s.size(); i++)
h[i] = (h[i - 1] * seed % p + ctoh(s[i])) % p;
}
inline hash_t extract(const int l, const int r)
{
if (l == 0)
return h[r];
else
return (h[r] - h[l - 1] * pow[r - l + 1] % p + p) % p;
}
}
int st[N], ed[N];
inline int lcp(const int len, const int a, const int b)
{
using Hash::extract;
int l = 1, r = len, ans = 0;
while (l <= r)
{
int mid = (l + r) >> 1;
if (extract(a, a + mid - 1) == extract(b, b + mid - 1))
l = mid + 1, ans = mid;
else
r = mid - 1;
}
return ans;
}
inline int lcs(const int len, const int a, const int b)
{
using Hash::extract;
int l = 1, r = len, ans = 0;
while (l <= r)
{
int mid = (l + r) >> 1;
if (extract(a - mid + 1, a) == extract(b - mid + 1, b))
l = mid + 1, ans = mid;
else
r = mid - 1;
}
return ans;
}
int work()
{
ios::sync_with_stdio(false);
int T;
Hash::init();
cin >> T;
while (T--)
{
string str;
cin >> str;
memset(st, 0, sizeof(int[str.size()]));
memset(ed, 0, sizeof(int[str.size()]));
Hash::get_hash(str);
for (int i = 1; i < str.size(); i++)
for (int j = 0; j + i < str.size(); j += i)
{
int nxt = j + i;
int pre = lcp(min(i, (int)str.size() - nxt + 1), j, nxt);
int suf = lcs(min(i, j + 1), j, nxt);
int sta = min(j - suf + 1, (int)str.size() - (i << 1));
int end = min(j + pre - i, (int)str.size() - (i << 1));
if (pre + suf - 1 >= i)
{
++st[sta], --st[end + 1];
++ed[sta + (i << 1) - 1], --ed[end + (i << 1)];
}
}
for (int i = 1; i < str.size(); i++)
st[i] += st[i - 1], ed[i] += ed[i - 1];
ll ans = 0;
for (int i = 1; i < str.size(); i++)
ans += (ll)ed[i - 1] * st[i];
cout << ans << '\n';
}
return 0;
}
}
int main()
{
return zyt::work();
}

三、\(O(n\log n)\)的后缀数组优秀做法

#include <iostream>
#include <cstring>
#include <algorithm>
#include <string>
using namespace std;
namespace zyt
{
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
const int N = 3e4 + 10, B = 15, CH = 26;
struct ST
{
int st[N][B];
const int *arr;
static int lg2[N];
static bool lg2_built;
int min(const int a, const int b)
{
return arr[a] < arr[b] ? a : b;
}
void build(const int n, const int *_arr)
{
arr = _arr;
if (!lg2_built)
{
int tmp = 0;
for (int i = 0; i < N; i++)
{
lg2[i] = tmp;
if (i == (1 << (tmp + 1)))
++tmp;
}
lg2_built = true;
}
for (int i = n - 1; i >= 0; i--)
{
st[i][0] = i;
for (int j = 1; j <= lg2[n]; j++)
if (i + (1 << j) - 1 < n)
st[i][j] = min(st[i][j - 1], st[i + (1 << (j - 1))][j - 1]);
else
break;
}
}
inline int query(const int l, const int r)
{
int len = lg2[r - l + 1];
return min(st[l][len], st[r - (1 << len) + 1][len]);
}
};
int ST::lg2[N];
bool ST::lg2_built;
struct Suffix_Array
{
int sa[N], rank[N], tp[N], count[N], height[N], kind, len;
ST st;
inline int min(const int a, const int b)
{
return height[a] < height[b] ? a : b;
}
void radix_sort()
{
static int count[N];
memset(count, 0, sizeof(int[kind]));
for (int i = 0; i < len; i++)
++count[rank[tp[i]]];
for (int i = 1; i < kind; i++)
count[i] += count[i - 1];
for (int i = len - 1; i >= 0; i--)
sa[--count[rank[tp[i]]]] = tp[i];
}
void build(const string &s)
{
len = s.size();
for (int i = 0; i < len; i++)
rank[i] = s[i] - 'a', tp[i] = i;
kind = CH;
radix_sort();
for (int tmp = 1; tmp < len; tmp <<= 1)
{
int cnt = 0;
for (int i = len - tmp; i < len; i++)
tp[cnt++] = i;
for (int i = 0; i < len; i++)
if (sa[i] >= tmp)
tp[cnt++] = sa[i] - tmp;
radix_sort();
swap(rank, tp);
rank[sa[0]] = 0;
kind = 1;
for (int i = 1; i < len; i++)
if (tp[sa[i]] == tp[sa[i - 1]] &&
(sa[i] + tmp < len && sa[i - 1] + tmp < len) &&
(tp[sa[i] + tmp] == tp[sa[i - 1] + tmp]))
rank[sa[i]] = rank[sa[i - 1]];
else
rank[sa[i]] = kind++;
if (kind == len)
break;
}
int k = 0;
for (int i = 0; i < len; i++)
{
if (!rank[i])
continue;
if (k)
--k;
int j = sa[rank[i] - 1];
while (i + k < len && j + k < len && s[i + k] == s[j + k])
++k;
height[rank[i]] = k;
}
st.build(len, height);
}
}sa1, sa2;
int st[N], ed[N];
inline int lcp(const int a, const int b)
{
int ra = sa1.rank[a], rb = sa1.rank[b];
return sa1.height[sa1.st.query(min(ra, rb) + 1, max(ra, rb))];
}
inline int lcs(const int len, const int a, const int b)
{
int ra = sa2.rank[len - a - 1], rb = sa2.rank[len - b - 1];
return sa2.height[sa2.st.query(min(ra, rb) + 1, max(ra, rb))];
}
int work()
{
ios::sync_with_stdio(false);
int T;
cin >> T;
while (T--)
{
string str, rev;
cin >> str;
memset(st, 0, sizeof(int[str.size()]));
memset(ed, 0, sizeof(int[str.size()]));
sa1.build(str);
rev = str, reverse(rev.begin(), rev.end());
sa2.build(rev);
for (int i = 1; i < str.size(); i++)
for (int j = 0; j + i < str.size(); j += i)
{
int nxt = j + i;
int pre = min(i, lcp(j, nxt));
int suf = min(i, lcs(str.size(), j, nxt));
int sta = min(j - suf + 1, (int)str.size() - (i << 1));
int end = min(j + pre - i, (int)str.size() - (i << 1));
if (pre + suf - 1 >= i)
{
++st[sta], --st[end + 1];
++ed[sta + (i << 1) - 1], --ed[end + (i << 1)];
}
}
for (int i = 1; i < str.size(); i++)
st[i] += st[i - 1], ed[i] += ed[i - 1];
ll ans = 0;
for (int i = 1; i < str.size(); i++)
ans += (ll)ed[i - 1] * st[i];
cout << ans << '\n';
}
return 0;
}
}
int main()
{
return zyt::work();
}

【洛谷1117_BZOJ4650】[NOI2016] 优秀的拆分(哈希_后缀数组_RMQ)的更多相关文章

  1. bzoj 4650(洛谷 1117) [Noi2016]优秀的拆分——枚举长度的关键点+后缀数组

    题目:https://www.lydsy.com/JudgeOnline/problem.php?id=4650 https://www.luogu.org/problemnew/show/P1117 ...

  2. 【洛谷4770/UOJ395】[NOI2018]你的名字(后缀数组_线段树合并)

    题目: 洛谷4770 UOJ395 分析: 一个很好的SAM应用题-- 一句话题意:给定一个字符串\(S\).每次询问给定字符串\(T\)和两个整数\(l\).\(r\),求\(T\)有多少个本质不同 ...

  3. 洛谷P2336 [SCOI2012]喵星球上的点名(后缀数组+莫队)

    我学AC自动机的时候就看到了这题,想用AC自动机结果被学长码风劝退-- 学后缀数组时又看到了这题--那就写写后缀数组做法吧 结果码风貌似比当年劝退我的学长还毒瘤啊 对所有的模式串+询问串,不同串之间用 ...

  4. 【BZOJ4560】[NOI2016]优秀的拆分

    [BZOJ4560][NOI2016]优秀的拆分 题面 bzoj 洛谷 题解 考虑一个形如\(AABB\)的串是由两个形如\(AA\)的串拼起来的 那么我们设 \(f[i]\):以位置\(i\)为结尾 ...

  5. [NOI2016]优秀的拆分&&BZOJ2119股市的预测

    [NOI2016]优秀的拆分 https://www.lydsy.com/JudgeOnline/problem.php?id=4650 题解 如果我们能够统计出一个数组a,一个数组b,a[i]表示以 ...

  6. luogu1117 [NOI2016]优秀的拆分

    luogu1117 [NOI2016]优秀的拆分 https://www.luogu.org/problemnew/show/P1117 后缀数组我忘了. 此题哈希可解决95分(= =) 设\(l_i ...

  7. [UOJ#219][BZOJ4650][Noi2016]优秀的拆分

    [UOJ#219][BZOJ4650][Noi2016]优秀的拆分 试题描述 如果一个字符串可以被拆分为 AABBAABB 的形式,其中 A 和 B 是任意非空字符串,则我们称该字符串的这种拆分是优秀 ...

  8. [NOI2016]优秀的拆分(SA数组)

    [NOI2016]优秀的拆分 题目描述 如果一个字符串可以被拆分为 \(AABB\) 的形式,其中 A和 B是任意非空字符串,则我们称该字符串的这种拆分是优秀的. 例如,对于字符串 \(aabaaba ...

  9. 洛谷P1712 [NOI2016]区间 尺取法+线段树+离散化

    洛谷P1712 [NOI2016]区间 noi2016第一题(大概是签到题吧,可我还是不会) 链接在这里 题面可以看链接: 先看题意 这么大的l,r,先来个离散化 很容易,我们可以想到一个结论 假设一 ...

随机推荐

  1. CodeForces 800B Volatile Kite(点与直线的距离)(Java 实现)

    CodeForces 800B Volatile Kite(点与直线的距离)(Java 实现) 传送门 如果想要一个凸多边形不退化为凹多边形,那么任意的相邻的三个点必然最多形成一条直线.因此我们可以求 ...

  2. Spring MVC概述(2)

    1.Spring 为展现层提供基于MVC设计理念的优秀的Web框架,是目前最主流的MVC框架之一. 2.Spring 3.0后全面超越Struts2,成为最优秀的MVC框架. 3.Spring MVC ...

  3. 演练:使用VS2010 C# 创作简单的多线程组件

    BackgroundWorker 组件取代了 System.Threading 命名空间并添加了功能:但是,可以选择保留 System.Threading 命名空间以实现向后兼容并供将来使用.有关更多 ...

  4. PHP array_pad()

    定义和用法 array_pad() 函数向一个数组插入带有指定值的指定数量的元素. 语法 array_pad(array,size,value) 参数 描述 array 必需.规定数组. size 必 ...

  5. Cisco路由器配置ADSL上网

    cisco1841#sh run Building configuration... Current configuration : 2970 bytes ! version 12.4 service ...

  6. 一起talk C栗子吧(第一百二十三回:C语言实例--显示变量和函数的地址)

    各位看官们,大家好,上一回中咱们说的是多线程的样例.这一回咱们说的样例是:显示变量和函数的地址. 闲话休提,言归正转.让我们一起talk C栗子吧! 在编敲代码时,有时候须要获取程序中变量和函数的地址 ...

  7. 加壳学习笔记(三)-简单的脱壳思路&amp;调试思路

    首先一些windows的经常使用API:   GetWindowTextA:以ASCII的形式的输入框   GetWindowTextW:以Unicaode宽字符的输入框   GetDlgItemTe ...

  8. C#之快速排序

    算法描述 1.假定数组首位元素为“枢轴”,设定数列首位(begin)与末位(end)索引: 2.由末位索引对应元素与“枢轴”进行比较,如果末位索引对应元素大于“枢轴”元素,对末位索引减一(end--) ...

  9. Java程序学习中各阶段的建议

    第一部分:对于尚未做过Java工作的同学,包括一些在校生以及刚准备转行Java的同学. 一.Java基础 首先去找一个Java的基础教程学一下,这里可以推荐一个地址,或者你也可以参照这个地址上去找相应 ...

  10. Hackerrank Connected Cell in a Grid

    Problem Statement You are given a matrix with m rows and n columns of cells, each of which contains ...