AGC030E [* hard]
给的两个长度为 (n) 的 01
串 (s,t),串的连续段长度不超过 (2),每次可以将 (s) 的一个位置反转,要求操作后连续段长度仍然不超过 (2),求使得 (s,t) 相等的最小操作次数。
(nle 5000)
Solution
考虑 (s_i e s_{i+1}) 的下标位置,假设 (s_i=0) 我们标记此处为 (0),否则标记为 (1)
例如 001001011
标记为 .01.010.1
我们不难发现 (s) 的标记序列是一个 01
交错的形式。
然后我们发现一次操作其实是将一个标记往左/右移。
我们发现最后的目标其实是将所有标记对齐。
为了方便处理,我们强制让标记的开头为 (0)。
唯一的问题是对两端进行操作时导致答案的改变并不满足我们的规则。
所以我们给两端补上 (infty) 个交错的标记。
我们发现操作序列合法等价于任意两个相邻的标记距离都不大于 (2)
我们发现答案的下界是将标记对齐后的下标差的和。
事实上,下界总是可以达到的,这是因为初始状态合法,操作的过程中我们每次先抵着再走即可。
所以我们只需要枚举从 (infty) 个交错的标记中额外取出多少个标记即可,然后 (mathcal O(n)) 计算答案,总体复杂度是 (mathcal O(n^2)) 的。
要注意在一开始保证 (s) 生成的标记序列大于 (t)(反之亦然)然后用 (t) 去对齐。
(Code:)
#include<bits/stdc++.h>
using namespace std ;
#define Next( i, x ) for( register int i = head[x]; i; i = e[i].next )
#define rep( i, s, t ) for( register int i = (s); i <= (t); ++ i )
#define drep( i, s, t ) for( register int i = (t); i >= (s); -- i )
#define re register
int gi() {
char cc = getchar() ; int cn = 0, flus = 1 ;
while( cc < '0' || cc > '9' ) { if( cc == '-' ) flus = - flus ; cc = getchar() ; }
while( cc >= '0' && cc <= '9' ) cn = cn * 10 + cc - '0', cc = getchar() ;
return cn * flus ;
}
const int N = 5000 + 5 ;
const int inf = 1e9 + 7 ;
int n, g[N], f[N * 3], d[N * 3], cnt, num, Ans ;
char s[N], t[N] ;
signed main()
{
n = gi(), Ans = inf ;
scanf("%s", s + 1 ) ;
scanf("%s", t + 1 ) ;
rep( i, 1, n ) s[i] -= '0', t[i] -= '0' ;
if(t[1] == 1) g[++ num] = 0 ; //begin with 0
rep( i, 1, n - 1 ) if(t[i] != t[i + 1]) g[++ num] = i ;
if(t[n] == 1) g[++ num] = n ; int u = (num / 2) * 2 ;
rep(i, 1, u) f[++ cnt] = 0 ;
if(s[1] == 1) f[++ cnt] = 0 ;
rep( i, 1, n - 1 ) if(s[i] != s[i + 1]) f[++ cnt] = i ;
if(s[n] == 1) f[++ cnt] = n ;
rep( i, 1, num ) f[++ cnt] = n ;
for(re int i = 0; i <= cnt - num; i += 2) {
int ans = 0 ;
rep( j, 1, i ) d[j] = 0 ;
rep( j, i + 1, i + num ) d[j] = g[j - i] ;
rep( j, i + num + 1, cnt ) d[j] = n ;
rep( j, 1, cnt ) ans += abs(f[j] - d[j]) ;
Ans = min( Ans, ans ) ;
}
cout << Ans << endl ;
return 0 ;
}