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 ∣S∣≤106
似乎杂题的时候遇见过呢。。。
这题是个乱搞好题,反正看网上若干篇博客都感觉不一样。
这里说说我的乱搞做法:
显然有这样一条性质:对于一个字符串来说,如果有个子序列满足相连的字母不同,那么它一定可以通过如此操作:每次删去形如 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 cntA≥cntB≥cntC。
假如只删 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 cntA−z=cntB−z+cntC−z,即 z = c n t B + c n t C − c n t A z=cnt_B+cnt_C-cnt_A z=cntB+cntC−cntA
我们先只做删 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 BABAB…AB这样的。可以发现,无论后面怎么操作,都不会出现必须做删 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+cntC−cntA。证明:设 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 B−z≤LB,也就是 A − L B ≤ C A-L_B\le C A−LB≤C,此时存在的 A A A要么夹在 B B B之间要么夹在 C C C之间, A − L B A-L_B A−LB意味着夹在 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+cntC−cntA+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+cntB−cntA+y−x,如果它更优,就有 y − x > S A y-x>S_A y−x>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 BABC→BAC这种形式,又因为 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=cntB≥cntC。 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+cntC−cntA=cntC,以下证明 c n t A − c n t C ≤ L B cnt_A-cnt_C\le L_B cntA−cntC≤LB:变一下就是 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 cntA−LB−LC≤cntC−LC,这样可以等价成一个只有所有 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+cntC−cntA,适当得删些 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;
}