zoukankan      html  css  js  c++  java
  • AtCoder Grand Contest 026 (AGC026) E

    原文链接https://www.cnblogs.com/zhouzhendong/p/AGC026E.html

    题目传送门 - AGC026E

    题意

      给定一个长度为 $2n$ 的字符串,包含 $n$ 个 $'a'$ 和 $n$ 个 $'b'$ 。

      现在,让你按照原顺序取出一些字符,按照原顺序组成新的字符串,输出所有满足条件的字符串中字典序最大的?(字典序: $'b'>'a'>' '$)

      条件限制:当且仅当取了原序列的第 $i$ 个 $'a'$ 时,原序列的第 $i$ 个 $'b'$ 也被取了。

      $nleq 3000$

    题解

      比赛结束前 30 分钟想到正解。

      赛后 20 多分钟 AC 了。

      第一次开黑好爽啊。

      早知道就把做 D 题的时间搬到后面来了(写了一半 xza AC 了)。这样的话就可以打到 30 多名了。

      扯淡完毕,言归正传。

      我们首先考虑一个显然的东西:

      开头要有尽量多个 $b$ 。

      然后,我们再考虑个东西。

      当你已经选择了一些 $b$ 之后,不可避免的来了个 $a$ ,你如果选了这个 $a$ ,答案最大为什么?

      bbbbba...a...a....a....a.....

      假设之前选择的 b 的对应 a 如上所述,那么为了保证字典序最大,我们得取光所有在前 4 个省略号区间里面的 b 。

      而这些 b 所对应的 a 必然在上述最后一个 a 后面。因为这些新选的 a 所对应的 b 在之前那些 a 所对应的 b 的后面。

      在选择完这些新的 b 和 a 之后,样子大概变成了这样:

      bbbbbabbabbbabbbabbaba......a....a...a

      于是你可以发现我们由要在新生的省略号区域取 $b$ 来保证最优。

      那么不断这样做,什么时候结束呢?

      当这一串  a 的个数 = b 的个数 时。

      我们称选择这些 a 和 b 并结束,为一次“区间贪心变换”。注意这个定义到后面我会讲的模糊些,与这里写的意义可能会有一点偏差。

      那么我们要在一个新的串中求解原来的求最大字典序串的问题。

      这个新的串就是原串除掉你当前选择的这一段之后留下的后缀串。

      我们在求后缀串答案的时候,又会去求后缀串的后缀串的答案。

      于是我们可以观察一下哪些后缀串会被求解。

      事实上,如果一个后缀串包含的 $a$ 的个数与包含的 $b$ 的个数相同,那么就会被求解。

      我们再回到原问题。

      我们称第 $i$ 个后缀的左端点为第 $i$ 个停止点,(最后一个停止点在 $n+1$ 位置,代表空后缀)

      那么,任何一个后缀进行一次“区间贪心变换”之后,一定会到达下一个停止点。

      这个东西的证明,留给你了。

      于是,一个后缀的求解结果就是他的任何“区间贪心变换”结果与下一个停止点开始的后缀的求解结果拼接的最大值。

      为什么说“任何“区间贪心变换””?显然,有可能有多种非常优秀的区间贪心变换。即多种前缀 b 个数最多的区间贪心变换。

      这个东西用个队列就可以了。

      于是这个问题就转化成了一个分阶段贪心的题目,可以理解成贪心 + DP 。

      注意一下,当前后缀的答案还可以直接从下一个后缀继承。

      

      当然,还有一种特殊情况!

      最优区间贪心变换的前缀 b 个数可能为 $0$ 。我们可以发现,这种情况下,每一个 b 对应的 a 都在它前面。

      我们可以考虑贪心的取形如 $abcdots ab$ 串,并每多取一个 $ab$ 就和下一个后缀一起更新一下当前的 dp 值。

      然后就完了。时间复杂度 $O(n^2)$ 。(可以写一些简单的优化来加速,但是这样的复杂度已经足够通过了)

      只可惜刚开始写的时候没想到后面这种特殊情况。 

      我可能是思考的不够透彻,导致我写出来的可能有点凌乱。

      但是我把代码写出来 AC 了。

      欢迎提问。

      一篇深入理解的简洁明了的题解我就先欠着吧……

    代码

    #include <bits/stdc++.h>
    using namespace std;
    const int N=6005;
    int n,p[N],pp[N],t[N];
    int q[N],head,tail;
    string res[N],now;
    char s[N];
    vector <int> a,b;
    int main(){
    	scanf("%d",&n);
    	n<<=1;
    	scanf("%s",s+1);
    	a.clear(),b.clear();
    	for (int i=1;i<=n;i++)
    		if (s[i]=='a')
    			a.push_back(i);
    		else
    			b.push_back(i);
    	for (int i=1;i<=n/2;i++)
    		p[b[i-1]]=a[i-1],pp[a[i-1]]=b[i-1];
    	for (int i=1;i<=n+1;i++)
    		res[i]="";
    	int tot=0;
    	int nxt=n+1;
    	for (int i=n;i>=1;i--){
    		if (s[i]=='a')
    			tot++;
    		else
    			tot--;
    		if (tot)
    			continue;
    		memset(t,0,sizeof t);
    		head=1,tail=0;
    		res[i]=res[nxt];
    		for (int j=i;j<nxt;j++){
    			if (s[j]=='a'){
    				if (t[j]){
    					now="";
    					for (int k=i;k<nxt;k++)
    						if (s[k]=='b'){
    							if (t[k]||k>j)
    								now+="b";
    						}
    						else if (t[k]||k>p[q[tail]])
    							now+="a";
    					res[i]=max(res[i],now+res[nxt]);
    					head++;
    					t[j]=t[pp[j]]=0;
    				}
    				continue;
    			}
    			if (p[j]<j)
    				continue;
    			q[++tail]=j;
    			t[j]=t[p[j]]=1;
    		}
    		memset(t,0,sizeof t);
    		if (tail==0){
    			for (int j=i;j<nxt;j++){
    				if (s[j]=='b')
    					continue;
    				t[j]=t[pp[j]]=1;
    				now="";
    				for (int k=i;k<=pp[j];k++)
    					if (t[k])
    						now+=s[k];
    				res[i]=max(res[i],now+res[nxt]);
    				j=pp[j];
    			}
    		}
    		nxt=i;
    	}
    	cout << res[1];
    	return 0;
    }
    

      

  • 相关阅读:
    VSCode添加git bash作为默认终端
    Git无法提交branch is currently checked out
    Excel创建下拉列表限制数据有效性
    Windows添加管理员用户
    从Windows10中彻底删除【3D对象】文件夹
    异常处理机制
    泛型
    Java集合
    String、StringBuffer、StringBulider
    System类与Runtime类
  • 原文地址:https://www.cnblogs.com/zhouzhendong/p/AGC026E.html
Copyright © 2011-2022 走看看