AGC036E ABC String

AGC036E

有个由 A , B , C A,B,C A,B,C组成的字符串,要找到其中最长的一个子序列,满足:

A , B , C A,B,C A,B,C出现次数相等。

子序列中相连的字母不同。

∣ S ∣ ≤ 1 0 6 |S|\le 10^6 S106


似乎杂题的时候遇见过呢。。。

这题是个乱搞好题,反正看网上若干篇博客都感觉不一样。

这里说说我的乱搞做法:

显然有这样一条性质:对于一个字符串来说,如果有个子序列满足相连的字母不同,那么它一定可以通过如此操作:每次删去形如 B A C BAC BAC中的 A A A,或者 B A B BAB BAB中的 A B AB AB(或 B A BA BA)得到,并且满足删的过程中一直满足相连的字母不同。

接下来假设 c n t A ≥ c n t B ≥ c n t C cnt_A\ge cnt_B \ge cnt_C cntAcntBcntC

假如只删 A B AB AB A C AC AC,设 z z z为每一个字母剩下的出现次数,那么有 c n t A − z = c n t B − z + c n t C − z cnt_A-z=cnt_B-z+cnt_C-z cntAz=cntBz+cntCz,即 z = c n t B + c n t C − c n t A z=cnt_B+cnt_C-cnt_A z=cntB+cntCcntA

我们先只做删 A A A的操作,再只做 A B AB AB A C AC AC的操作。那么我们就要最小化 c n t A cnt_A cntA(当然最小也要大于等于 c n t B cnt_B cntB),所以在 c n t A > c n t B cnt_A>cnt_B cntA>cntB时尽量找可以删的 A A A,然后删之。

假如删去的所有的可以删的 A A A,那么不存在形如 B A C BAC BAC的结构,只会存在 B A B A B … A B BABAB\dots AB BABABAB这样的。可以发现,无论后面怎么操作,都不会出现必须做删 A A A操作才能够到达的状态(也就是说即使有也可以通过删 A B AB AB之类的替换),所以只操作删 A B AB AB操作在当前状况下是最优的。这时候 z z z可以直接取到 c n t B + c n t C − c n t A cnt_B+cnt_C-cnt_A cntB+cntCcntA。证明:设 L B , L C L_B,L_C LB,LC分别为可以删 A B AB AB A C AC AC最多次数。如果 z z z符合条件,必有 B − z ≤ L B B-z\le L_B BzLB,也就是 A − L B ≤ C A-L_B\le C ALBC,此时存在的 A A A要么夹在 B B B之间要么夹在 C C C之间, A − L B A-L_B ALB意味着夹在 C C C之间的 A A A的个数,所以显然不会超过 C C C L C L_C LC的证明同理。设 S A S_A SA表示删 A A A操作的最多次数,那么 c n t B + c n t C − c n t A + S A cnt_B+cnt_C-cnt_A+S_A cntB+cntCcntA+SA是此时的答案。这样在当前状况下得到的最优的答案。

这同时也是全局状况下最优解的答案,证明(这里的证明比较感性,不够严谨,期待更好的证明):假如我们通过这个方法确定了一个方案,不难发现改变操作顺序没有任何影响,所以这是只有删 A A A A B AB AB A C AC AC操作下的最优解。如果要更优,那么就可能要引入删 B B B C C C甚至 B C BC BC的操作。不考虑删 C C C B C BC BC操作(这种操作一眼就感觉不存在。。。),设删 B B B操作要做 x x x次,删 A A A操作要做 y y y次。那么有 z = c n t C + c n t B − c n t A + y − x z=cnt_C+cnt_B-cnt_A+y-x z=cntC+cntBcntA+yx,如果它更优,就有 y − x > S A y-x>S_A yx>SA,所以 y > x + S A y>x+S_A y>x+SA,之前定义了 S A S_A SA是删 A A A操作的最多次数,要是满足这个条件,这个最多次数一定在操作过程中发生了增加,增加无非就是 B A B C → B A C BABC\to BAC BABCBAC这种形式,又因为 B A C BAC BAC中的 A A A是消耗品,所以最终变成 B C BC BC。这和直接删 A B AB AB是等价的。于是引入 B B B操作就是相当于 A B AB AB拆成删 B B B和删 A A A,没有本质突破。所以最优解由删 A A A A B AB AB A C AC AC的得到,即上面所述答案。

假如没有删去所有可以删的 A A A,那么这时候一定有 c n t A = c n t B ≥ c n t C cnt_A=cnt_B\ge cnt_C cntA=cntBcntC z z z可以取到 c n t B + c n t C − c n t A = c n t C cnt_B+cnt_C-cnt_A=cnt_C cntB+cntCcntA=cntC,以下证明 c n t A − c n t C ≤ L B cnt_A-cnt_C\le L_B cntAcntCLB:变一下就是 c n t A − L B − L C ≤ c n t C − L C cnt_A-L_B-L_C\le cnt_C-L_C cntALBLCcntCLC,这样可以等价成一个只有所有 A A A都在形如 B A C BAC BAC形式的字符串,且不存在 A B A ABA ABA A C A ACA ACA。假如不成立,由于一个 B A C BAC BAC带着一个 A A A C C C,我们希望 c n t A ′ > c n t C ′ cnt_A'>cnt_C' cntA>cntC,自然是要让 C C C被出现在两组 B A C BAC BAC之中,就会出现形如 B A C A B BACAB BACAB的结构,然而这个时候是存在了一个 A C A ACA ACA的,于是就矛盾了。证完之后,我们得到:如果只删 A B AB AB,那么能够达到答案是 c n t C cnt_C cntC。显然 c n t C cnt_C cntC同时也是答案的上限,所以这样在当前状况下也能得到最优答案,同时也是全局状况下的最优答案。

综上,我们得到了一个策略:先做删 A A A的操作,删到不能删或者 c n t A = c n t B cnt_A=cnt_B cntA=cntB为止。然后取 z = c n t B + c n t C − c n t A z=cnt_B+cnt_C-cnt_A z=cntB+cntCcntA,适当得删些 A B AB AB A C AC AC。这样就得到了一个正确的做法。

话说有些证明都是我写题解的时候脑补出来的呢,当时切题的时候拍了好久没有问题就直接干了。


using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cassert>
#define N 1000010
int n;
char str[N];
int cnt[3],p[3],re[3];
void adjust(int &A,int &B,int &C){
	memset(cnt,0,sizeof cnt);
	for (int i=1;i<=n;++i)
		cnt[str[i]-'A']++;
	for (int i=0;i<3;++i)
		p[i]=i;
	for (int i=0;i<3;++i)
		for (int j=1;j<3;++j)
			if (cnt[p[j-1]]<cnt[p[j]])
				swap(p[j-1],p[j]);
	for (int i=0;i<3;++i)
		re[p[i]]=i;
	for (int i=1;i<=n;++i)
		str[i]=re[str[i]-'A']+'A';
	A=cnt[p[0]],B=cnt[p[1]],C=cnt[p[2]];
}
struct LIST{
	LIST *pre,*suc;
	char c;
} s[N];
void erase(LIST *p){
	p->pre->suc=p->suc;
	p->suc->pre=p->pre;
}
bool BAC(LIST *p){return p->c=='A' && p->pre->c!=p->suc->c;}
bool BAB(LIST *p){return !p->pre->c || !p->suc->c || p->pre->c==p->suc->c;}
int main(){
//	freopen("in.txt","r",stdin);
//	freopen("out.txt","w",stdout);
	scanf("%s",str+1);
	n=strlen(str+1);
	int tmp=0;
	for (int i=1;i<=n;++i)
		if (str[i]!=str[i-1])
			str[++tmp]=str[i];
	n=tmp;
	str[n+1]=0;
	int A,B,C;
	adjust(A,B,C);
	if (C==0)
		return 0;
//	printf("%s\n",str+1);
	s[0].suc=&s[1];
	s[n+1].pre=&s[n];
	for (int i=1;i<=n;++i)
		s[i]={&s[i-1],&s[i+1],str[i]};
	for (LIST *p=s[0].suc;p!=&s[n+1] && A>B;p=p->suc)
		if (BAC(p)){
			A--;
			erase(p);			
		}	
//	for (LIST *p=s[0].suc;p!=&s[n+1];p=p->suc)
//		putchar(p->c);
//	printf("\n");
	int z=B+C-A;
	int neB=B-z,neC=C-z;
	for (LIST *p=s[0].suc;p!=&s[n+1];p=p->suc){
		if (BAB(p)){
//			for (LIST *p=s[0].suc;p!=&s[n+1];p=p->suc)
//				putchar(p->c);
//			printf("\n");
			if (p->c=='A'){
				if ((p->pre->c|p->suc->c)=='B')
					{if (neB) erase(p->pre->c?p->pre:p->suc),erase(p),neB--;}
				else
					{if (neC) erase(p->pre->c?p->pre:p->suc),erase(p),neC--;}
			}
			else if ((p->pre->c|p->suc->c)=='A'){
				if (p->c=='B')
					{if (neB) erase(p->pre->c?p->pre:p->suc),erase(p),neB--;}
				else
					{if (neC) erase(p->pre->c?p->pre:p->suc),erase(p),neC--;}
			}
		}
	}
	for (LIST *q=s[0].suc;q!=&s[n+1];q=q->suc)
		putchar(p[q->c-'A']+'A');
	return 0;
}
	
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值