比赛链接:
https://codeforces.com/contest/1680
C. Binary String
题目大意:
给定一个二进制字符串,可以从头部和尾部删除一个子串,最后的价值为删除的 1 的数量和剩下的 0 的数量的最大值,问这个价值最小是多少。
思路:
考虑暴力做法,枚举剩下的字符串的起点和终点,\(O(n^2)\),超时。
如果固定了左端点,可以发现右端点的移动对答案的影响是单调的,当右端点向右移动时,剩下的 0 的数量增加,删除的 1 的数量减少,向左移动时,剩下的 0 的数量减少,删除的 1 的数量增加。可以通过二分求答案。
也可以固定右端点,通过双指针移动,当左指针向右移动时,删除的 1 的数量增加,剩下的 0 的数量减少,右指针向右移动时,删除的 1 的数量减少,剩下的 0 的数量增加,当二者达到平衡的时候,就是值最小的时候。
二分
#include<bits/stdc++.h>
using namespace std;
int T;
string s;
void solve(){
cin >> s;
int n = s.size(), ans = n + 1, sum = 0;
vector <int> cnt0(n + 1, 0), cnt1(n + 1, 0);
for (int i = 0; i < n; i ++ ){
cnt0[i + 1] = cnt0[i] + (s[i] == '0');
cnt1[i + 1] = cnt1[i] + (s[i] == '1');
sum += (s[i] == '1');
}
ans = min(ans, cnt1[n] - cnt1[0]);
for (int i = 0; i < n; i ++ ){
int l = i, r = n;
while (l < r){
int mid = l + r >> 1;
int a = sum - (cnt1[mid + 1] - cnt1[i]), b = cnt0[mid + 1] - cnt0[i];
if (a >= b){
ans = min(ans, max(a, b));
l = mid + 1;
}
else r = mid;
}
}
cout << ans << "\n";
}
int main(){
cin >> T;
while (T -- )
solve();
return 0;
}
双指针
#include<bits/stdc++.h>
using namespace std;
int T;
string s;
void solve(){
cin >> s;
int n = s.size(), ans = n + 1, sum = 0;
vector <int> cnt0(n + 1, 0), cnt1(n + 1, 0);
for (int i = 0; i < n; i ++ ){
cnt0[i + 1] = cnt0[i] + (s[i] == '0');
cnt1[i + 1] = cnt1[i] + (s[i] == '1');
sum += (s[i] == '1');
}
ans = min(ans, cnt1[n] - cnt1[0]);
for (int l = 0, r = 0; r < n; ){
while (l < r && sum - (cnt1[r + 1] - cnt1[l]) < cnt0[r + 1] - cnt0[l])
l ++ ;
ans = min(ans, max(sum - (cnt1[r + 1] - cnt1[l]), cnt0[r + 1] - cnt0[l]));
r ++ ;
}
cout << ans << "\n";
}
int main(){
cin >> T;
while (T -- )
solve();
return 0;
}
E. Moving Chips
题意:
有一个 2 * \(n\) 的板,'.' 表示空,'*' 表示一个芯片,每次可以移动某个芯片向其他位置移动(不能移出板),当两个芯片移动到同一个格子的时候,一个芯片会消失,问最少几步可以让板中的芯片只剩下一个。
思路:
容易想到,肯定有一种方案可以让所有的芯片都移动到最右边的那个芯片,且操作步数最少,即没有后效性,所以可以想到 \(dp\)。
定义 \(dp[i][j]\) 为将前 \(j\) 列的所有芯片都放到第 \(i\) 行的最少步数,容易得到转移方程。
代码:
#include <bits/stdc++.h>
using namespace std;
#define LL long long
void solve(){
LL n;
cin >> n;
vector < vector<char> > s(2, vector<char> (n));
for (int i = 0; i < 2; i ++ )
for (int j = 0; j < n; j ++ )
cin >> s[i][j];
LL L = -1, R = 0;
for (int j = 0; j < n; j ++ ){ //找到左右边界
if (s[0][j] == '*' || s[1][j] == '*'){
if (L == -1){
L = j;
R = j;
}
else{
R = j;
}
}
}
vector < vector<LL> > dp(2, vector<LL> (n));
dp[0][L] = (s[1][L] == '*');
dp[1][L] = (s[0][L] == '*');
for (int j = L + 1; j <= R; j ++ ){
if (s[0][j] == '.' && s[1][j] == '.'){
dp[0][j] = min(dp[0][j - 1] + 1, dp[1][j - 1] + 2);
dp[1][j] = min(dp[0][j - 1] + 2, dp[1][j - 1] + 1);
}
else if (s[0][j] == '.' && s[1][j] == '*'){
dp[0][j] = min(dp[0][j - 1] + 2, dp[1][j - 1] + 2);
dp[1][j] = min(dp[0][j - 1] + 2, dp[1][j - 1] + 1);
}
else if (s[0][j] == '*' && s[1][j] == '.'){
dp[0][j] = min(dp[0][j - 1] + 1, dp[1][j - 1] + 2);
dp[1][j] = min(dp[0][j - 1] + 2, dp[1][j - 1] + 2);
}
else{
dp[0][j] = min(dp[0][j - 1] + 2, dp[1][j - 1] + 2);
dp[1][j] = min(dp[0][j - 1] + 2, dp[1][j - 1] + 2);
}
}
cout << min(dp[0][R], dp[1][R]) << "\n";
}
int main(){
ios::sync_with_stdio(false);cin.tie(0);
LL T;
cin >> T;
while(T -- ){
solve();
}
return 0;
}