【AtCoder Grand Contest 029 C】Lexicographic constraints

二分答案,贪心,栈。

题目大意:

给定一个大小为 $n$ 的字符串数组 $s_1,s_2,\ldots,s_n$。

已知第 $i$ 个字符串的长度为 $A_i$。

求字符集大小至少为多少,使得存在满足条件的字符串数组,其字典序严格递增

题解:

答案显然满足可二分性,考虑二分答案并且判断字符集为 $c$ 是否可行。

我们从小到大确定每个字符串,每次令其为比上一个字符串字典序大的,字典序最小的字符串。这样保证最后一个是最小的。

若 $A_i\lt A_j$,则在 $s_i$ 后补上 $A_j-A_i$ 个最小的字符即可。

若 $A_i\geq A_j$,则取 $s_i$ 的长度为 $A_j$ 的前缀,并令最后一个字符加一即可。若超过 $c$ 则往前进位,以此类推。

我们用栈记录所有不是最小的字符的位置以及其字符。

若 $A_i\lt A_j$,则栈中无变化。

若 $A_i\geq A_j$,则弹出栈中位置超过 $A_j$ 的元素,然后若不存在 $A_j$,则插入倒数第二小的字符,否则令其加一。若产生进位,则删掉这个位置,往前考虑,直到进位结束或发现无法进位。若无法进位则这个 $c$ 不可行。

如果 $n$ 个串都成功确定,则 $c$ 可行。

每次只会删除若干个栈中元素,插入最多 $1$ 个元素,修改最多 $1$ 个原有元素。每个栈中元素只会被删一次,因此时间复杂度 $O(n)$。

答案为 $1$ 的情况可以特判,不难发现当且仅当 $A_i$ 严格递增时,答案为 $1$。

时间复杂度 $O(n\log V)$。

Code:

#include<cstdio>
const int N=2e5+6;
int n,A[N];
int a[N],b[N],top;
bool check(int c){
    top=0;
    for(int i=2;i<=n;++i)
    if(A[i]<=A[i-1]){
        int pos=A[i];
        while(top&&a[top]>pos)--top;
        if(!top||a[top]!=pos)a[++top]=pos,b[top]=1;else{
            while(top&&a[top]==pos&&b[top]==c-1)--top,--pos;
            if(!pos)return 0;
            if(top&&a[top]==pos)++b[top];else a[++top]=pos,b[top]=1;
        }
    }
    return 1;
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;++i)scanf("%d",A+i);
    bool ok=1;
    for(int i=1;i<=n;++i)if(A[i]<=A[i-1])ok=0;
    if(ok){puts("1");return 0;}
    int l=2,r=1e9,ans=1e9;
    while(l<=r){
        const int mid=l+r>>1;
        if(check(mid))r=(ans=mid)-1;else l=mid+1;
    }
    printf("%d\n",ans);
    return 0;
}