题目链接


题目大意:

就是给你n个串每个串取一个后缀,要求把串拼起来要求字典序最小!!
sum_length_of_n≤5e5sum\_length\_of\_n\leq 5e5sum_length_of_n≤5e5


MY Slove :

  1. 首先我们知道对于最后一个串肯定是取最小后缀的

  2. 那么我们可以把最后一个串的结果接到倒数第二个串上面在求一次最小后缀就可以得倒数第二个串的结果,依次以此类推就好了。

  3. 但是这样我们算算复杂度:假如我们每次都把答案拼到下一个串上面求个后缀数组那么我们假设最坏就是a+zzzzz......a+zzzzz......a+zzzzz......取均摊的复杂度就是n\sqrt nn​那么就是每个串长度是n\sqrt nn​,有n\sqrt nn​个串,答案最坏的情况是全部取,那么你每次都拼接的话,最后一个串要动n\sqrt nn​次,倒数第二个要动n−1\sqrt n-1n​−1次…那么最坏就是O(n(1+n)/2+n(∑i=1ni∗log(i∗n)))O(\sqrt n(1+\sqrt n)/2+ \sqrt n(\sum_{i=1}^{\sqrt n}i*log(i*\sqrt n)))O(n​(1+n​)/2+n​(∑i=1n​​i∗log(i∗n​))) 铁定T飞了(但是实践证明O(n2)O(n^2)O(n2)都可以过的假题)

  4. 我们考虑优化:

  5. 我们想想后面那一大坨的问题就是:后缀数组求解拼接后字符串的时间复杂度是很大的n(∑i=1ni∗log(i∗n))\sqrt n(\sum_{i=1}^{\sqrt n}i*log(i*\sqrt n))n​(∑i=1n​​i∗log(i∗n​)) 那么我们可不可以考虑只对原来的字符求后缀数组,去虚假拼接求呢?

  6. 我们看看后缀排序后的后缀长什么样子:
    eg:aba
    a
    aba
    b

  7. 通过观察我们知道要拼接的答案肯定是以最小后缀为前缀的后缀的其中一个这个贪心看肯定是这样的
    我们看看这个数据就知道了

3
ababac
b
drl
ans:ababacbdrl

那么我们可以顺着这个串的所有后缀比下去,但是这个串所有后缀字符个数是O(n2)O(n^2)O(n2)的?肯定不是这样比呀!!

因为我们知道前面是一样的,实际上我们每次只比较后个串多出来的那部分,那部分的长度是O(n)O(n)O(n)

  1. 但是如果前面都一样怎么办?我们就要看红色重叠的部分了,但是如果暴力比较红色部分复杂度还是将不下来!!因为最坏就是红色的长度了那么有nnn个后缀就是O(n2)O(n^2)O(n2)
  2. 因为后面是答案串我们可以对答案串进行hash,然后二分那个第一个hash值第一个hash值不一样的位置进行判断字典序!
  3. 我们在动态维护答案串的hash,不能正序hash,因为字符是头插入的,那么我们逆着hash

AC code

细节巨多,局难写,写了一晚上

#include <bits/stdc++.h>
#define mid ((l + r) >> 1)
#define Lson rt << 1, l , mid
#define Rson rt << 1|1, mid + 1, r
#define ms(a,al) memset(a,al,sizeof(a))
#define log2(a) log(a)/log(2)
#define _for(i,a,b) for( int i = (a); i < (b); ++i)
#define _rep(i,a,b) for( int i = (a); i <= (b); ++i)
#define for_(i,a,b) for( int i = (a); i >= (b); -- i)
#define rep_(i,a,b) for( int i = (a); i > (b); -- i)
#define lowbit(x) ((-x) & x)
#define IOS std::ios::sync_with_stdio(0); cin.tie(0); cout.tie(0)
#define INF 0x3f3f3f3f
#define LLF 0x3f3f3f3f3f3f3f3f
#define hash Hash
#define next Next
#define pb push_back
#define f first
#define s second
using namespace std;
const int N = 1e6 + 10, mod = 1e9 + 9;
const int maxn = 5e5+10;
// const long double eps = 1e-5;
const int base = 233;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> PII;
typedef pair<ll,ll> PLL;
typedef pair<double,double> PDD;
template<typename T> void read(T &x) {x = 0;char ch = getchar();ll f = 1;while(!isdigit(ch)){if(ch == '-')f*=-1;ch=getchar();}while(isdigit(ch)){x = x*10+ch-48;ch=getchar();}x*=f;
}
template<typename T, typename... Args> void read(T &first, Args& ... args)  {read(first);read(args...);
}
//......................这里是后缀数组
// sa[l]排序是lth的后缀的开始位置
//ra[l]是起点是l的后缀排名多少
//lcp(suf(i),suf(j)) = min(H(ra[i] + 1),....H(ra[j]));
//区间最小值倍增求
// H(i)是rk[i]和rk[i-1]的lcp
//这个板子下标是从0开始,rank从1开始
//传进来的是int数组但是不能有0
struct SA { int sa[maxn], ra[maxn], height[maxn];int t1[maxn], t2[maxn], c[maxn];int shift[maxn];inline void init(const string &s) {for(int i = 0; i < s.size(); ++ i) shift[i] = (int)(s[i]-'a'+1);build(shift,s.size(),30);}void build(int *str, int n, int m) {//字符数组,长度,字符种类 str[n] = 0;n++;int i, j, p, *x = t1, *y = t2;for (i = 0; i < m; i++) c[i] = 0;//排序的桶for (i = 0; i < n; i++) c[x[i] = str[i]]++;for (i = 1; i < m; i++) c[i] += c[i - 1];for (i = n - 1; i >= 0; i--) sa[--c[x[i]]] = i;for (j = 1; j <= n; j <<= 1) {//所有长度为1,2,4,8....的子串的排序//长度够长的话就是所有的后缀排序p = 0;for (i = n - j; i < n; i++) y[p++] = i;for (i = 0; i < n; i++) if (sa[i] >= j) y[p++] = sa[i] - j;for (i = 0; i < m; i++) c[i] = 0;for (i = 0; i < n; i++) c[x[y[i]]]++;for (i = 1; i < m; i++) c[i] += c[i - 1];for (i = n - 1; i >= 0; i--) sa[--c[x[y[i]]]] = y[i];swap(x, y);p = 1;x[sa[0]] = 0;for (i = 1; i < n; i++)x[sa[i]] = (y[sa[i - 1]] == y[sa[i]] && y[sa[i - 1] + j] == y[sa[i] + j]) ? p - 1 : p++;if (p >= n) break;m = p;}n--;for (int i = 0; i <= n; i++) ra[sa[i]] = i;for (int i = 0, j = 0, k = 0; i <= n; i++) {if (k) k--;j = sa[ra[i] - 1];while (str[i + k] == str[j + k]) k++;height[ra[i]] = k;}st_init(height, n);}int lg[maxn], table[23][maxn];void st_init(int *arr, int n) {if (!lg[0]) {lg[0] = -1;for (int i = 1; i < maxn; i++)lg[i] = lg[i / 2] + 1;}for (int i = 1; i <= n; ++i)table[0][i] = arr[i];for (int i = 1; i <= lg[n]; ++i)for (int j = 1; j <= n; ++j)if (j + (1 << i) - 1 <= n)table[i][j] = min(table[i - 1][j], table[i - 1][j + (1 << (i - 1))]);//从j开始长度是2^i次方得st表}int lcp(int l, int r) {l = ra[l], r = ra[r];if (l > r) swap(l, r);++l;int t = lg[r - l + 1];//区间长度return min(table[t][l], table[t][r - (1 << t) + 1]);}
} sa;
//............................... 这里是Hash
string s[maxn], ans;
ull pw[maxn];
vector<ull> Hash;inline void insert(char a) {ull last = Hash.empty() ? 0ull : Hash.back();last = last * base + a - 'a' + 1;Hash.push_back(last);
}inline ull gethash(int l,int r) {if(r < l) return 0; return (ull)Hash[r]-(l - 1 < 0 ? 0ull : Hash[l-1])*pw[r-l+1];
}
//........................
inline void update(int i, int anspos) { // 更新答案for(int m = s[i].size()-ans.size()-1; m >= anspos; -- m) insert(s[i][m]), ans += s[i][m];
}int main() {IOS;pw[0] = 1;for(int i = 1; i < maxn; ++ i) pw[i] = pw[i-1] * base;int _;cin >> _;while (_--) {Hash.clear();int n;cin >> n;for(int i = 1; i <= n; ++ i) cin >> s[i];sa.init(s[n]);ans = s[n].substr(sa.sa[1]); // 先找到最后一个串的最小后缀for(int i = ans.size()-1; i >= 0; -- i) insert(ans[i]); // 逆着Hashreverse(ans.begin(),ans.end()); // 将答案串反过来for(int i = n - 1; i >= 1; -- i) { // 枚举第i个串if(s[i].size()==1) { insert(s[i][0]); ans += s[i]; continue;}// 特判长度为1的串// 对这个串建立后缀数组sa.init(s[i]);// 把答案串拼到新串后面for(int j = (int)ans.size()-1; j >= 0; -- j) s[i] += ans[j];int anspos = sa.sa[1]; //anspos = 答案后缀的开头位置for(int k = 2; k <= s[i].size()-ans.size(); ++ k) { // 枚举这个串的每个后缀if(sa.lcp(sa.sa[k],sa.sa[k-1]) != s[i].size()-ans.size()-anspos) { // 如果不是以前面后缀为前缀那就结束了后面答案不会更优update(i,anspos);break; } int eps = s[i].size() - ans.size() - sa.sa[k-1];int is = 0;// 只比较突出的部分int j;for(j = 0; j+anspos+eps < s[i].size() && j+sa.sa[k]+eps<s[i].size()-ans.size(); ++ j) if(s[i][j+anspos+eps] < s[i][j+sa.sa[k]+eps]) {// 如果在黑is = 1;update(i,anspos);break;} else if(s[i][j+anspos+eps] > s[i][j+sa.sa[k]+eps]) {anspos = sa.sa[k];if(k == s[i].size()-ans.size()) update(i,anspos);//如果到了最后一个串了可以直接更新了is = 2;break;} else if(j+anspos+eps == s[i].size()-1) {is = 1;update(i,anspos);break;                       }if(is == 1) break; // is == 1 表示可以确定答案了提前退出就好了if(is == 2) continue;//...................................二分红色部分int l = 0, r = s[i].size()-(j+anspos+eps)-1;//注意我是把答案串拼到了新串后面int tmp = j+anspos+eps-(s[i].size()-ans.size());while(l < r) {//注意Hash是逆序存的,要从末尾开始看if(gethash(Hash.size()-1-tmp-mid,Hash.size()-1-tmp)==gethash(Hash.size()-1-mid,Hash.size()-1)) l = mid+1;else r = mid;}if(s[i][j+anspos+eps+l]>s[i][j+sa.sa[k]+eps+l]) anspos = sa.sa[k];if(k == s[i].size()-ans.size()) update(i,anspos);//如果到了最后一个串了可以直接更新了} }for(int i = ans.size()-1;~i;i--) cout << ans[i];cout << endl;        }return 0;
}

Solution 2

贪心从后往前看,最后一个串一定选择字典序最小的后缀,然后把这个后缀拼接到第n - 1个串,重复这个步骤就行了。

具体实现:

从后往前遍历,每次找当前串的最小后缀,这个可以对于当前下标和当前最小后缀下标二分+hash找到lcp,看lcp后一个位置的大小,然后更新最小后缀的下标这里是关键)。然后把最小后缀拼接到前一个串即可。

理论时间复杂度跟后缀数组差不多,但后缀数组常数和编码量更大。


AC code

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef  unsigned long long ull;
const ull base=131;
#define mod
#define maxn 605000
ull p[maxn];ull h[maxn];
ull get_hash(ull l,ull mid)
{return h[l]-h[l-mid]*p[mid];
}
string str[maxn];char ans[maxn];
int len[maxn];
ull check(ull x,ull y)
{ull l=1,r=y;while(l<=r){ull mid=(l+r)/2;//cout<<"l="<<l<<" r="<<r<<" mid="<<mid<<'\n';if(get_hash(x,mid)==get_hash(y,mid)){l=mid+1;}else r=mid-1;}if(l==y+1) return 0;return ans[x-r]<ans[y-r];
}
int main()
{ull T;p[0]=1;for(ull i=1;i<=maxn-100;i++){p[i]=p[i-1]*base;}cin>>T;while(T--){ull n;cin>>n;for(ull i=1;i<=n;i++){cin>>str[i];len[i]=str[i].size();}ull id=0;for(ull i=n;i>=1;i--){//cout<<"i="<<i<<" id="<<id<<'\n';ans[++id]=str[i][len[i]-1];h[id]=h[id-1]*base+str[i][len[i]-1];ull to=1;ull t=id;for(int j=(int)(len[i])-2;j>=0;j--){//cout<<"i="<<i<<" j="<<j<<'\n';ans[id+to]=str[i][j];h[id+to]=h[id+to-1]*base+str[i][j];if(check(id+to,t)){t=id+to;}to++;}id=t;}for(ull i=id;i>=1;i--) cout<<ans[i];cout<<'\n';}
}
/*
3
3
bbb
aaa
ccc
*/

后缀数组 + Hash + 二分 or Hash + 二分 + 双指针 求 LCP ---- 2017icpc 青岛 J Suffix (假题!!)相关推荐

  1. 后缀数组--(最长公共前缀)

    问题描述:给一个字符串,询问某两个后缀的最长公共前缀. 解析:当然用后缀数组最方便,在后缀数组中有很多重要的定义和性质,现在我们来认识一些: 定义:LCP(i,j)=suffix(SA[i])与suf ...

  2. HDU 5769 Substring(后缀数组)

    Substring Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others) Total ...

  3. 1402 后缀数组 (hash+二分)

    描述 后缀数组 (SA) 是一种重要的数据结构,通常使用倍增或者DC3算法实现,这超出了我们的讨论范围.在本题中,我们希望使用快排.Hash与二分实现一个简单的 O(n log^2⁡n ) 的后缀数组 ...

  4. FJUT3703 这还是一道数论题(二分 + hash + manacher 或者 STL + hash 或者 后缀数组 + hash)题解...

    Problem Description 最后来个字符串签个到吧,这题其实并不难,所需的算法比较基础,甚至你们最近还上过课. 为了降低难度,免得所有人爆零.这里给几个提示的关键字 :字符串,回文,二分, ...

  5. [bzoj1717][Usaco2006 Dec]Milk Patterns 产奶的模式 (hash构造后缀数组,二分答案)

    以后似乎终于不用去学后缀数组的倍增搞法||DC3等blablaSXBK的方法了= = 定义(来自关于后缀数组的那篇国家集训队论文..) 后缀数组:后缀数组SA是一个一维数组,它保存1..n的某个排列S ...

  6. Hash(LCP) || 后缀数组 LA 4513 Stammering Aliens

    题目传送门 题意:训练指南P225 分析:二分寻找长度,用hash值来比较长度为L的字串是否相等. #include <bits/stdc++.h> using namespace std ...

  7. 【Codeforces - 127D】Password(思维,二分+字符串Hash)

    题干: Asterix, Obelix and their temporary buddies Suffix and Prefix has finally found the Harmony temp ...

  8. luoguP5108 仰望半月的夜空 [官方?]题解 后缀数组 / 后缀树 / 后缀自动机 + 线段树 / st表 + 二分...

    仰望半月的夜空 题解 可以的话,支持一下原作吧... 这道题数据很弱..... 因此各种乱搞估计都是能过的.... 算法一 暴力长度然后判断判断,复杂度\(O(n^3)\) 期望得分15分 算法二 通 ...

  9. 算法竞赛进阶指南---0x14(Hash)后缀数组

    题面 题解 我们先来看朴素做法,对于一个长度为n的字符串,它的后缀也有n个,将他们排序(用快排)是 O(nlogn) ,如果是暴力比较两个字符串的字典序是 O(n) ,那么总的时间复杂度就是O(n2l ...

最新文章

  1. JavaScript 对象的遍历以及判断方法
  2. C++知识点40——运算符的重载概念与分数类实现(中)
  3. 【luogu 3375】【模板】KMP字符串匹配
  4. golang 使用 http socks 代理
  5. python2.7安装pip_RobotFramework安装过程遇到的问题(电脑同时安装python2和3)
  6. java炸弹人素材_炸弹人图片_炸弹人模板_炸弹人设计素材下载
  7. 获取请求消息行信息案例代码
  8. 【转】Android虚拟平台的编译和整合
  9. filezilla 共享多个目录_Linux下搭建NFS文件共享服务器
  10. 怎么让图片铺满手机屏幕_手机屏幕密码忘了怎么解锁
  11. php中array怎么用,php中array()函数如何使用
  12. 小程序显示富文本内容(wxparse)
  13. 基于JSP的图书销售管理系统
  14. 华为认证的考试费用和重认证
  15. debian linux系统安装教程,Debian 10(Buster)安装过程图文详解
  16. 基于openCV和ZED的测距
  17. STK仿真日记之双星相位轨控
  18. 用java做小学数学系统_基于jsp的小学数学试卷生成-JavaEE实现小学数学试卷生成 - java项目源码...
  19. PHP对接 创蓝短信
  20. 幻影虚拟位置破解版连接不到服务器,ffbe幻影战争与服务器连接失败怎么办

热门文章

  1. 你可以恢复模糊的图像吗?
  2. 使用Python+OpenCV实现图像数据采集
  3. 只用一张训练图像进行图像的恢复
  4. 第三次Scream冲刺
  5. 三层交换机环境的上网行为管理方案
  6. Linux下分区、格式化、自动挂载
  7. Python 排序的姿势,你们,你们还要学习..学习一个
  8. 强大的矢量图形库:Raphael JS 中文帮助文档及教程
  9. Php 获取xml中的节点值
  10. nginx+tomcat实现集群负载均衡(实现session复制)