【题解】2019NWRRC L. Lengths and Periods 后缀数组,按height枚举

给一个字符串 w w w,求它的临界指数。

w w w 的子串 t t t ,它由它的一个前缀循环 α \alpha α 次构成, α \alpha α 可以是分数。

临界指数就是最大的 α \alpha α.


本来想按这道题的做法,枚举基值长度 r r r,在 r r r的所有倍数上枚举起始位置 s t st st,然后尽量朝左朝右扩展。

但这是错的,无法处理分数倍数的长度恰好卡在两个整数倍数之间的情况

比如abcdefghd,答案应该是 6 / 5 6/5 6/5,但按上述方法只能求出 1 / 1 1/1 1/1.

正确做法

正确的做法是先做后缀数组,求出sa和height.

对于任意 a < b a<b a<b,它们可以贡献一个答案 L C P + S U B S U B \frac {LCP+SUB}{SUB} SUBLCP+SUB,其中

L C P = m i n i = a + 1 b { h e i g h t [ i ] } LCP=min_{i=a+1}^{b}\{height[i]\} LCP=mini=a+1b{height[i]} S U B = ∣ s a [ a ] − s a [ b ] ∣ SUB=|sa[a]-sa[b]| SUB=sa[a]sa[b].

看到分数形式的最优化问题,自然想到分数规划…个屁啊。

我们可以枚举从大到小 L C P LCP LCP,然后求最小的 S U B SUB SUB.

如果把height数组看成直方图上的高度,从上到下扫描,横向会依次扩展。

每扩展一个方格,或者将两个矩形连通,实际上相当于做了集合合并。

我们用并查集来维护集合合并的过程,集合中存储的数字是 s a [ i ] sa[i] sa[i]

以上部分的思路和[NOI2015]品酒大会基本相同,但是并查集中需要查询的东西不同。

维护最小的差

对于每次集合合并,需要知道合并后集合中最近的两个数的差。

为此,可以用 s e t set set维护集合,启发式合并(把小的并入大的)来更新差。

合并之后要记得把小的集合清空。

复杂度分析

后缀数组 n l o g n nlogn nlogn,并查集认为是 O ( n ) O(n) O(n) s e t set set多一个 l o g log log,启发式合并多一个 l o g log log

总复杂度 n l o g 2 . nlog^2. nlog2.


总结:

  1. 一个愚蠢的wa点:合并集合时忘了插入。
  2. 后缀数组处理和lcp有关的问题非常实用,很多时候可以枚举height.
  3. 如果做过类似的题当然很好,但是一旦链接到一个错误的题上,就会浪费大量的时间与提交。
  4. nwrrc拿金太难了qwq
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int M = 200016, MOD = 1000000007;

inline int read()
{
	int x=0, f=1; char ch = getchar();
	while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
	while(isdigit(ch)){x=x*10+ch-'0'; ch=getchar();}
	return x*f;
}
/*
后缀从0开始,排名从1开始
sa[i]表示排名第i的后缀是谁,下标是[1,n],值是[0,n-1]
ra[i]表示第i个后缀的排名,下标是[0,n-1],值是[1,n]
height[i]表示排名第i的后缀与排名第i-1的后缀的LCP,下标是[2,n]
*/
namespace SA
{    
    void st_init(int *arr, int n);

    /* 后缀数组 */
    int sa[M], ra[M], height[M]; //后缀三数组,sa和ra下标从0开始,height下标从1开始
    int t1[M], t2[M], c[M]; // 用于基数排序的三个辅助数组
    void build(char *str, int n, int m) // 构造后缀三数组,字符串下标从0开始,n表示长度,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)  
        {  
            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);
    }

    /* ST表 */
    int lg[M];
    int table[20][M];
    void st_init(int *arr, int n)
    {
        if(!lg[0])
        {
            lg[0]=-1;
            for(int i=1;i<M;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))]);
    }
    // 查询第l个后缀和第r个后缀的LCP,下标从0开始
    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]);
    }
}


namespace UFS //迫真并查集
{
    int fa[M]; 
    int ans[M];
    set<int> st[M];

    void init(int n, int *sa)
    {
        for(int i=1; i<=n; ++i)
        {
            fa[i] = i;
            st[i].insert(sa[i]);
            ans[i] = n;
        }
    }
    int find(int n)
    {
        return fa[n]==n ? fa[n] : fa[n] = find(fa[n]);
    }
    int join(int a, int b)
    {
        a = find(a), b = find(b);
        if(st[a].size()>st[b].size()) swap(a,b);
        fa[a] = b;
        ans[b] = min(ans[a], ans[b]);
        for(auto x:st[a])
        {
            auto it = st[b].lower_bound(x);
            if(it!=st[b].end()) ans[b]=min(ans[b], *it-x);
            if(it!=st[b].begin()) ans[b]=min(ans[b], x-*(--it));
            st[b].insert(x);
        }
        st[a].clear();
        return ans[b];
    }
}

using P = pair<int,int>;
char str[M];
int main(void)
{
	#ifdef _LITTLEFALL_
	freopen("in.txt","r",stdin);
	#endif

	scanf("%s", str);
	int n = strlen(str);
	SA::build(str, n, 128);

    using SA::height; using SA::sa;
    UFS::init(n, sa);
    vector<P> vc(n-1);
    for(int i=2; i<=n; ++i)
        vc.push_back({height[i], i});
    sort(vc.begin(), vc.end(), greater<P>());


    P ans= {1,1};
    for(auto p:vc)
    {
        int sub = UFS::join(p.second,p.second-1);
        P tmp = {p.first + sub, sub};
        if(1ll*ans.first*tmp.second<1ll*tmp.first*ans.second)
            ans = tmp;
    }
    int gcd = __gcd(ans.first, ans.second);
    ans.first/=gcd, ans.second/=gcd;
    printf("%d/%d\n",ans.first,ans.second );


	return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值