[CF1411D] Grime Zoo - 贪心,组合
Description
给定由字符 0,1,? 组成的字符串 s 和参数 x,y,需要在所有字符为 ? 的位置填入 0 或者 1。这个字符串的价值为子序列 01 的数量乘以 x 加上 10 的数量乘以 y。求字符串价值的最小值。
Solution
存在一个分割点,分割点之前的 ? 全部填一个值,后面的全部填另一个值
在 x>y 时,我们希望多点 10,所以前面的 ? 填 1,后面的 ? 填 0,反过来同理
现在我们去枚举分割点,关键是怎样计算答案
假设分割点为 i
考虑前 0 后 1 的情况
额外的 01 个数,等于前面的 0 的个数乘以后面的 ? 个数,加上前面的 ? 个数乘以后面的 1 个数
考虑前 1 后 0 的情况
额外的 10 个数,等于前面的 1 的个数乘以后面的 ? 个数,加上前面的 ? 个数乘以后面的 0 个数
#include <bits/stdc++.h>
using namespace std;
#define int long long
signed main()
{
ios::sync_with_stdio(false);
string str;
int x, y;
cin >> str >> x >> y;
int n = str.length();
int c01 = 0, c10 = 0;
int c0 = 0, c1 = 0;
vector<int> a0(n + 2), a1(n + 2), b(n + 2), a(n + 2);
for (int i = 1; i <= n; i++)
{
a0[i] = str[i - 1] == '0';
a1[i] = str[i - 1] == '1';
b[i] = str[i - 1] == '?';
a0[i] += a0[i - 1];
a1[i] += a1[i - 1];
b[i] += b[i - 1];
if (str[i - 1] == '0')
c0++;
if (str[i - 1] == '1')
c1++;
if (str[i - 1] == '1')
c01 += c0;
if (str[i - 1] == '0')
c10 += c1;
}
auto f0 = [&](int l, int r) -> int { return a0[r] - a0[l - 1]; };
auto f1 = [&](int l, int r) -> int { return a1[r] - a1[l - 1]; };
auto fb = [&](int l, int r) -> int { return b[r] - b[l - 1]; };
int tans = 0;
int ans = 1e18;
for (int i = 1; i <= n; i++)
{
if (str[i - 1] == '0' || str[i - 1] == '?')
a[i] = 0, tans += y * f1(1, i - 1);
else
a[i] = 1, tans += x * (f0(1, i - 1) + fb(1, i - 1));
}
ans = min(ans, tans);
for (int i = 1; i <= n; i++)
if (str[i - 1] == '?')
{
a[i] = 1;
tans -= y * (f1(1, i - 1) + fb(1, i - 1)) + x * (f1(i + 1, n));
tans += x * (f0(1, i - 1)) + y * (f0(i + 1, n) + fb(i + 1, n));
ans = min(ans, tans);
}
tans = 0;
for (int i = 1; i <= n; i++)
{
if (str[i - 1] == '0')
a[i] = 0, tans += y * (f1(1, i - 1) + fb(1, i - 1));
else
a[i] = 1, tans += x * (f0(1, i - 1));
}
ans = min(ans, tans);
for (int i = 1; i <= n; i++)
if (str[i - 1] == '?')
{
a[i] = 0;
tans -= x * (f0(1, i - 1) + fb(1, i - 1)) + y * (f0(i + 1, n));
tans += y * (f1(1, i - 1)) + x * (f1(i + 1, n) + fb(i + 1, n));
ans = min(ans, tans);
}
cout << ans << endl;
}