HDU6761 Minimum Index Lyndon分解

题目描述

给你一个长度为N的串,求每个前缀的字典序最小的后缀的位置。
1 ≤ N ≤ 1 e 6 1\leq N \leq 1e6 1N1e6

分析

这个题可以用SAM做,但是会被卡,所以考虑用Lyndon分解来做
不懂Lyndon分解的可以看看我巨佬学弟博客的学习笔记
发现对于Lyndon分解完后的一个Lyndon串,整个Lyndon串的最小前缀肯定就是这个Lyndon串的开头。
我们可以对每一个Lyndon串做一次exkmp,这样假设从后往前扫,假设一个位置是 i i i,然后和这个串前缀相同的长度是 e x t e n d [ i ] extend[i] extend[i],那么他就可以对所有 [ i , i + e x t e n d [ i ] ) [i,i+extend[i]) [i,i+extend[i])中没有记录过答案的串记录答案。

其实也可以在做Lyndon分解中直接处理,我们假设 k k k是要拓展的字符, j j j k k k的在Lyndon串中相对应的位置,统计答案的时候,假设:
s [ k ] > s [ j ] s[k] > s[j] s[k]>s[j]那么肯定 k k k这个位置可以和前面的Lyndon串合并,形成新的Lyndon串,所以记录答案 p [ k ] = i p[k] = i p[k]=i
s [ k ] = s [ j ] s[k] = s[j] s[k]=s[j]由于 s [ i , j ] s[i,j] s[i,j] s [ k − ( j − i ) , k ] s[k-(j-i),k] s[k(ji),k]是一样的,那么 k k k这个位置的答案就相应继承 j j j的答案就好了, p [ k ] = p [ j ] + ( k − j ) p[k] = p[j]+(k-j) p[k]=p[j]+(kj)
最后如果在 i i i移动后,新建的一个Lyndon串中, p [ i ] = i p[i]=i p[i]=i就好了

代码

#include 

#define rep(i,a,b) for(int i=(a);i<=(b);++i)
#define dep(i,a,b) for(int i=(a);i>=(b);--i)
#define fi first
#define se second
#define CL clear
#define MP make_pair
#define PB push_back

#define int long long

const int N = (int) 1e6 + 10;
const int mod = (int) 1e9+7;

using namespace std;

typedef long long ll;

inline int rd() {
  int p=0; int f=1; char ch=getchar();
  while(ch<'0' || ch>'9'){if(ch=='-') f*=-1; ch=getchar();}
  while(ch>='0' && ch<='9'){p=p*10+ch-'0'; ch=getchar();}
  return p*f;
}

char s[N]; int p[N];

signed main() {
  int t = rd();
  while(t--) {
  scanf("%s",s+1); int n = strlen(s+1);
  for(int i=1;i<=n;) {
    int j = i; int k = i+1; p[i] = i;
    while(j<=n && s[j] <= s[k]) {
	    if(s[j] < s[k]) j = i,p[k] = i;
	    else p[k] = p[j] + k - j , j++;
	    k++;
		}while(i<=j) i+=k-j;
	}
	
	int bas = 1; int ans = 0;
  for(int i=1;i<=n;++i) ans = (ans + bas * p[i] % mod) % mod , bas = bas * 1112 % mod;
  printf("%lld\n",ans);
  for(int i=1;i<=n;++i) p[i] = 0;
  }
  return 0;
}

你可能感兴趣的:(hdu,Lyndon分解)