• 「CEOI2016」match 题解


    进行了一些修改,现在应该变得更清晰一些。

    2021/10/22 UPD:之前的打法时间复杂度假了,故进行了修正。

    题意

    \(~~~~\) 求字典序最小的匹配括号串使得匹配的一对括号在给定字符串 \(s\) 中对应的字符相同。或说明无解。
    \(~~~~\) \(1\leq |s|\leq 10^5.\)

    题解

    \(~~~~\) 先来判有没有解,可以直接把字符串当成括号串来放进栈里面匹配,栈顶与当前字符相同说明这样可以放匹配括号,则弹出,否则入栈。最后只要栈为空就是有解的。

    \(~~~~\) 首先考虑怎么求某一个区间是否合法(但在正常的做题思路中它应该在后面),显然如果在栈没有扫到这个区间与刚好扫过这个区间之后栈内的字符是相同的则该区间合法。记 \(hash_i\) 表示在 \(i\) 字符执行完进栈/弹栈操作后的栈内的 \(hash\) 值,那么当 \(hash_{l-1}=hash_r\) 时,区间 \([l,r]\) 是合法的。

    \(~~~~\) 那么怎么来求解某个合法区间呢?我们记 \(solve(l,r)\) 为求解一个已知区间 \([l,r]\) 的答案的过程,我们现在可以知道:

    • \(l\) 处应放置左括号 :显然,因为区间合法,第一个字符只有放左括号才可能匹配上后面的字符。
    • \(l\) 处匹配的右括号应该放在可能的最远的位置,设为 \(pos\),那么\([pos+1,r]\) 会被划分为一个区间,则 \(pos\) 应该满足 \(hash_{pos}=hash_r\)\(s_l=s_{pos}\)
    • 同时为了满足字典序更小,右括号应该被放在尽可能远的位置,所以 \(pos\) 应取可行的最大值。

    \(~~~~\) 关于怎么快速找到 \(pos\) 可行的最大值?用 map 维护最大的位置 \(j\) ,满足 \(s_j\) 为给定字符与给定hash值,同时顺便维护某个位置上一个条件与它相同的位置,最后暴力跳即可。

    \(~~~~\) 这样,区间就被我们分成了三个部分:\(l\)\(pos\)\([l+1,pos-1]\)\([pos+1,r]\) 。由于 \(hash_{pos}=hash_{r}\) ,所以区间 \([pos+1,r]\) 是合法的。而区间 \([l,pos]\) 也一定是合法的,在合法区间两边同时去掉一位,若这两位对应的字符相同,余下的区间也一定合法。(这两个字符在栈里面会最后消掉,故扫描到最后一位之前一定与第一位刚进栈时栈内字符串相等)所以 \([l+1,pos-1]\) 也一定合法。

    \(~~~~\) 那既然这两个区间是合法的,我们可以继续执行 \(solve\) 过程就可以求解这两个区间。


    2021/10/22 UPD

    \(~~~~\) 但是,求出全局最靠右的满足该条件的位置再暴力跳回来的复杂度是假的 \(n \log n\),因为当所有字符相同时,正如上面一篇题解所述复杂度应为 \(0+1+\dots+(\frac{n}{4}-1)=\frac{n^2}{16}\),所以这个办法其实会被卡。

    \(~~~~\) 解决方法是:每次先遍历右区间 \([pos+1,r]\),同时更新 map 存储的某一条件的位置到尽可能靠左的地方,这样由于从右往左处理区间,所以每次 map 找到的位置都是在当前区间右边但尽可能靠近它的位置,这样暴力跳的次数就不会太多。当然你也可以把 \(hash\) 值离散化后用 vector 存下来,每次二分合适的位置,只是会带大常数。

    \(~~~~\) 时间复杂度 \(\mathcal{O(n\log n)}\).

    代码

    #include <map>
    #include <stack>
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    #define mp(a,b) make_pair(a,b)
    #define ll long long
    #define ull unsigned long long
    using namespace std;
    const ull base=131;
    char Ans[100005],str[100005],sta[100005];
    int lef[100005],lst[100005];
    ull P[100005],Hash[100005];
    int Top;
    map<pair<ll,int>,int>M;
    void solve(int l,int r)
    {
    	if(r<=l) return;
    	int pos=M[mp(Hash[r],str[l]-'a')];
    	while(pos>r) pos=lef[pos];
    	Ans[l]='(';Ans[pos]=')';
    	M[mp(Hash[pos],str[pos]-'a')]=pos;
    	solve(pos+1,r);solve(l+1,pos-1);
    }
    int main() {
    	scanf("%s",str+1);
    	int len=strlen(str+1);
    	P[0]=1;
    	for(int i=1;i<=len;i++) P[i]=P[i-1]*base;
    	ull now=0;
    	for(int i=1;i<=len;i++)
    	{
    		if(str[i]==sta[Top]) now-=P[Top--]*(str[i]-'a'+1);
    		else now+=P[++Top]*(str[i]-'a'+1),sta[Top]=str[i];
    		Hash[i]=now;
    	}
    	if(Top) return puts("-1")&0;
    	for(int i=1;i<=len;i++)
    	{
    		lef[i]=M[mp(Hash[i],str[i]-'a')];
    		M[mp(Hash[i],str[i]-'a')]=i;	
    	}
    	solve(1,len);
    	puts(Ans+1);
    	return 0;
    }
    
  • 相关阅读:
    node.js 版本管理工具 — nvm 下载、安装、配置及使用
    UNION [ All ]使用注意事项
    IDEA无法启动问题
    python + jinja2 + pandas 输出HTML报告
    PGbadger 慢日志分析工具
    密码复杂度策略 密码有效期 登陆失败策略
    postgresql 监控 洞察力
    linux 出错 “INFO: task xxxxxx: 634 blocked for more than 120 seconds.”的3种解决方案
    fsck 命令来修复磁盘
    could not locate a valid checkpoint record
  • 原文地址:https://www.cnblogs.com/Azazel/p/14440069.html
Copyright © 2020-2023  润新知