题解:P12550 [UOI 2025] Reversal ABC
提供一个不需要暴力优化 dp 的做法。
手玩一下这个交换容易发现,当我们交换一对之后,这两个数会彼此锁定。比如
进一步地,我们发现左边的
根据冒泡排序结论,这个交换次数是固定的,我们只能交换“逆序对”(
由于我们每次会把一个子串交换到锁死状态,所以子串的结构之间没有影响。所以我们实际上是对这个串进行划分,使得所有段的“逆序对”数量和最大。直接 dp 就是
考虑优化。我们先证明几个结论:
Lemma 1. 不存在一种优的划分,划分两边的字符相同。
Proof. 考虑这个切点所在的极长连续段,现在我们把它分成了左半边和右半边,在两个段分别贡献“逆序对”。而总有一边贡献得多一点,我们把切点往少的那边挪,让多的那边字符多一些总是不劣的,所以我们一定可以把切点移到连续段的端点处。Lemma 2. 不存在一种优的划分,有相邻两个段当中出现的字符集合相同。
Proof. 显然,把这两个段合并在一起一定会让“逆序对”数量增多。
据此我们考虑转移
于是我们把这两个点拿出来转移就可以了。使用两个前缀和计算转移时产生的逆序对即可。
用双指针写即可做到
int tc;
int n;
char c[N];
ll dp[N];
int sum[N][3];
ll psum[N][3];
int ed[N],cut[N];
bool check(int l,int r){
return !((sum[r][2]-sum[l-1][2])&&(sum[r][1]-sum[l-1][1])&&(sum[r][0]-sum[l-1][0]));
}
ll inv(int l,int r,int op1,int op2){//op1>op2
return psum[l][op1]-psum[r+1][op1]-1ll*(sum[r][op1]-sum[l-1][op1])*(sum[n][op2]-sum[r][op2]);
}
ll cinv(int l,int r){
if(sum[r][2]-sum[l-1][2]&&sum[r][1]-sum[l-1][1]){
return inv(l,r,1,2);
}
else if(sum[r][2]-sum[l-1][2]&&sum[r][0]-sum[l-1][0]){
return inv(l,r,2,0);
}
else if(sum[r][1]-sum[l-1][1]&&sum[r][0]-sum[l-1][0]){
return inv(l,r,0,1);
}
return 0;
}
void solve(){
cin>>n;
fr1(i,1,n){
cin>>c[i];
fr1(j,0,2) sum[i][j]=sum[i-1][j];
sum[i][c[i]-'A']++;
}
psum[n+1][0]=psum[n+1][1]=psum[n+1][2]=0;
fr2(i,n,1){
fr1(j,0,2) psum[i][j]=psum[i+1][j];
psum[i][c[i]-'A']+=sum[n][(c[i]-'A'+1)%3]-sum[i][(c[i]-'A'+1)%3];
}
int lst=n;
ed[n]=n;
fr2(i,n-1,1){
if(c[i]!=c[i+1]) lst=i;
ed[i]=lst;
}
fr1(i,1,n) dp[i]=-1e18;
int r=0;
fr1(l,1,n){
while(!check(r+1,l)) r++;
cut[l]=r;
}
fr1(i,1,n){
int tf=cut[i];
dp[i]=max(dp[tf]+cinv(tf+1,i),dp[i]);
dp[i]=max(dp[ed[tf+1]]+cinv(ed[tf+1]+1,i),dp[i]);
}
cout<<dp[n]<<'\n';
}
int main(){
#ifdef Shun
freopen(".in","r",stdin);
freopen(".out","w",stdout);
#endif
ios::sync_with_stdio(false);
cin>>tc;
while(tc--) solve();
ET;
}
//ALL FOR Zhang Junhao.