1 条题解

  • 0
    @ 2025-8-24 22:51:08

    自动搬运

    查看原文

    来自洛谷,原作者为

    avatar ningago
    恋爱脑

    搬运于2025-08-24 22:51:08,当前版本为作者最后更新于2023-12-29 21:44:06,作者可能在搬运后再次修改,您可在原文处查看最新版

    自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多

    以下是正文


    前缀的字典序最小后缀问题,考虑 Lyndon 分解。称 Lyndon 分解的结果为 Lyndon 块,记第 ii 个 Lyndon 块为 TiT_i

    对于 pip_i,根据定义有以下性质:

    • [pi,i][p_i,i] 是 Lyndon 串。
    • [j,i](j<pi)[j,i](j<p_i) 不是 Lyndon 串。

    也即,pip_i[1,i][1,i] 的所有后缀中最长的 Lyndon 串。

    通过 pnp_n,可以得出 nn 所在的 Lyndon 分解的左端点 lnl_n,而通过 pln1p_{l_n-1} 可以得到下一个 Lyndon 分解的段。一直迭代下去,就可以通过 pp 求出原串的 Lyndon 分解

    令一个点 ii 所在的 Lyndon 块的左端点和右端点为 li,ril_i,r_i

    考虑在 Duval 算法中,若 jj 是当前考虑的点,kkjj 在已经求好的循环节中的对应点,则此时需要对 sj<sk/sj=sk/sj>sks_j<s_k/s_j=s_k/s_j>s_k 进行讨论。

    可以考虑模拟 Duval 算法:

    • sj<sks_j<s_k,此时相当于划分 Lyndon 块,因为求出了 l,rl,r,可以不管这种情况。(对每个块独立求解)
    • sj>sks_j>s_k,此时相当于缩了一个大循环节,判断条件为 pj=ljp_j=l_j
    • sj=sks_j=s_k,此时相当于让循环长度增加,判定条件为 jpj=kpkj-p_j=k-p_k
    • 上述两条件都不满足,即为无解。

    通过模拟 Duval 算法,可以求出:

    • preipre_i 表示当 j=ij=i 时,对应的 kk 是谁。
    • valival_i 表示是 si=spreis_i=s_{pre_i} 还是 si>spreis_i>s_{pre_i}。令前者为 00 后着为 11

    此时如果整个串只有一个 Lyndon 块,就可以根据条件从前往后字典序贪心(val=0val=0 就照抄,否则加一)。

    而有多个 Lyndon 块时,新的条件为 TiTi+1T_i\geq T_{i+1}

    此时还是可以字典序贪心,但是按块 TiT_i 从后往前(块内从前往后贪心)。

    贪心时如果遇到当前位置比 Ti+1T_{i+1} 的对应位置小,分两种情况进行调整:

    • vali=1val_i=1,则可以直接照抄为 Ti+1T_{i+1} 的对应元素。
    • vali=0val_i=0,则需要让字典序在 [1,i1][1,i-1] 就定胜负,贪心的方法是:
      • 把上一个 valj=1val_j=1 的点加一,然后将 (j,i](j,i] 重新贪心分配字符(因为会对后面进行影响)。
    • 由于进行一次第二种调整后,字典序就严格大于 Ti+1T_{i+1} 了,所以第二种调整每个块只会进行一次。复杂度是对的。

    最后若 TiT_iTi+1T_{i+1} 的严格前缀,也可以做第二种调整。

    复杂度 O(n)O(n)

    代码参考了隔壁题解,Orz。

    #define N 3000010
    int n, m;
    int p[N], last[N], s[N]; 
    #define NO { printf("-1\n"); return; }
    int pre[N]; bool val[N];
    int lst(int x) { if(x > m) return 0; return last[x]; }
    void solve()
    {
    	m = 0;
    	n = read(); for(int i = 1; i <= n; i++) p[i] = read();
    	for(int r = n, l; r >= 1; r = l - 1)
    	{
    		val[l = p[r]] = 1; for(int i = l; i <= r; i++) if(p[i] < l) NO;
    		for(int i = l + 1, len = 1; i <= r; i++)
    		{
    			pre[i] = i - len;
    			if(p[i] == l) { val[i] = 1; len = i - l + 1; }
    			else if(i - p[i] == i - len - p[i - len]) val[i] = 0;
    			else NO;
    		}
    		bool big = 0;
    		s[l] = lst(1);
    		for(int i = l + 1, j; i <= r; i++)
    		{
    			s[i] = s[pre[i]] + val[i];
    			if(s[i] > lst(i - l + 1)) big = 1;
    			if(!big && s[i] < lst(i - l + 1))
    			{
    				if(val[i]) s[i] = lst(i - l + 1);
    				else { for(j = i; !val[j]; j--); s[i = j]++; big = 1; }
    			}
    		}
    		if(!big && r - l + 1 < m)
    		{
    			int i, j;
    			for(j = r; !val[j]; j--); s[i = j]++; big = 1;
    			for(i++; i <= r; i++) s[i] = s[pre[i]] + val[i];
    		}
    		m = r - l + 1;
    		for(int i = l; i <= r; i++) last[i - l + 1] = s[i];
    	}
    	for(int i = 1; i <= n; i++) print(s[i] + 1, ' '); putc('\n');
    }
    
    • 1

    信息

    ID
    9257
    时间
    3000ms
    内存
    1024MiB
    难度
    7
    标签
    递交数
    0
    已通过
    0
    上传者