[JZOJ5374]永远的三日天下

题目大意

给你一个长度为n的字符串s,要求你构造一个长度为n的括号序,满足:

  1. 是合法的括号序
  2. 匹配的一对括号下标对应的字符相同
  3. 字典序最小

n150000

分析

在考虑字典序最小之前,考虑如何构造合法括号序。这是前提。
我们用贪心构造出括号序,可以证明任何合法的括号序都可以变为贪心这一种。
就是把字符加进栈里面,如果新加入的和栈顶相同就两个都退掉,形成一对匹配的括号。
那么暴力的话,我们可以考虑从前往后填左括号,然后枚举它匹配的右括号(尽量靠后),再判断分割出的两个区间合不合法,这样就能构造字典序最小的了。
我们需要找一些性质来做这道题。
设f(l,r)=1表示区间[l,r]能够构造合法括号序。
那么若f(l,x)=1,f(x+1,r)=1,则f(l,r)=1;
设对1~n贪心时,处理完第i位的点后,单调栈的情况特征值为f[i],(hash值或者trie节点编号)
f(l,r)和s[l]==s[r]且f[l-1]==f[r]互为充要条件。

现在考虑用分治,一开始的时候,我们找到最大的,满足f(1,x)=1的x,令1为左括号,x为右括号,这样一定不会比贪心的构造字典序大。这其实跟暴力是一个方法。
然后(2,x-1)和(x+1,n)分治下去构造,由于第一个性质,后面那个区间肯定也是合法的。
现在问题只剩下如何找x了。
考虑上面的性质2。我们记链表数组pos[i][j],表示字符为i,特征值为j的位置从小到大有哪些。我们每次做的时候直接取链表最后一个有效的位置就可以了,只要分治的时候先做右边的区间,这个就可以用指针维护。
复杂度线性。

代码

#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<set>
#include<bitset>
using namespace std;
#define fo(i,j,k) for(i=j;i<=k;i++)
#define fd(i,j,k) for(i=j;i>=k;i--)
#define cmax(a,b) (a=(a>b)?a:b)
typedef long long ll;
typedef double db;
const int N=4e6+5;
int n,i;
int tr[N][26],fa[N],tmp,f[N],par[N],st,sta[N],rt;
int first[26][N],next[N],b[N],tt;
char s[N];
void cr(int x,int y,int z)
{
    tt++;
    b[tt]=z;
    next[tt]=first[x][y];
    first[x][y]=tt;
}
void solve(int l,int r)
{
    if (l>=r) return ;
    while (par[b[first[s[l]-'a'][f[l-1]]]]) first[s[l]-'a'][f[l-1]]=next[first[s[l]-'a'][f[l-1]]];
    int pos=b[first[s[l]-'a'][f[l-1]]];
    first[s[l]-'a'][f[l-1]]=next[first[s[l]-'a'][f[l-1]]];
    par[l]=-1;
    par[pos]=1;
    solve(pos+1,r);
    solve(l+1,pos-1);
}
int main()
{
    freopen("t2.in","r",stdin);
    freopen("seija.out","w",stdout);
    scanf("%s",s+1);
    n=strlen(s+1);
    rt=tmp=1;
    f[0]=1;
    fo(i,1,n)
    {
        if (st&&s[sta[st]]==s[i])
        {
            tmp=fa[tmp];
            sta[st--]=0;    
        }else
        {
            sta[++st]=i;
            if (!tr[tmp][s[i]]) tr[tmp][s[i]]=++rt,fa[rt]=tmp;
            tmp=tr[tmp][s[i]];
        }
        f[i]=tmp;
    }
    if (st)
    {
        printf("-1\n");
        return 0;
    }
    fo(i,1,n)
        cr(s[i]-'a',f[i],i);
    solve(1,n);
    fo(i,1,n) if (par[i]<0) printf("(");else printf(")");
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值