Codeforces Round #782 (Div. 2)
A. Red Versus Blue
题意
红队和蓝队正在比赛,给出比赛场次 \(n\),红队赢的场次 \(a\), 蓝队赢的场次 \(b\)
求出一个比赛情况,使得每个队伍连续获胜的场次最少。
思路
-
\(a >= b\), 那么问题可以转化为 在 \(b + 1\) 个 空隙 中,填 \(a\),使得连续的 \(a\) 最少
我们河狸分配 \(a / (b + 1)\) 以及 \(a % (b + 1)\) 即可 -
\(a < b\) 我们直接交换 \(a, b\) 即可
$\color{red}{SOLUTION} $
点击查看代码
void solve() {
int n, a, b; cin >> n >> a >> b;
char c1 = 'R', c2 = 'B';
if(a < b) {
swap(a, b); swap(c1, c2);
}
int x = a / (b + 1), y = a % (b + 1);
string ans;
string res(x, c1);
//处理最左边那个空隙
ans = res;
if(y) {
ans += c1;
y --;
}
for(int i = 0; i < b; i ++ ) {
ans += c2;
ans += res;
if(y) {
ans += c1;
y --;
}
}
cout << ans << "\n";
}
B. Bit Flipping
题意
给定一个长度为 \(n\) 且只包含 \(0\) 和 \(1\) 的字符串,对这个字符串进行 \(k\) 次操作、
每次操作可以选择一个字符,并且把除了这个字符之外的字符都 \(0\) 变 \(1\), \(1\) 变 \(0\)
求出恰好 \(k\) 次操作后的字符串转成十进制的最大值
思路
我们对 \(k\) 分 奇、偶 讨论
当 \(k\) 为奇数的时候,从高位到低位进行贪心,假设当前位为 \(1\),那么我们就要使得 \(k\) 次操作中有奇数次是选择的当前位,这样可以保证当前位最后是 \(1\),那么我们可以让这一位操作 \(1\) 次 (这样可以有更多的操作次数去让后面的位都置一);假设当前位为 \(0\),那么我们就要使得 \(k\) 次操作中有偶数次是选择的当前位,这样可以保证当前为最后是 \(1\),那么我们可以让这一位操作 \(0\) 次
这样操作完,我们可以尽可能的保证高位为 \(1\)
假如操作次数仍有剩余,我们可以将所有的次数都给最后一位即可,这样可以保证答案是最优解。
\(k\) 为偶数同理。
$\color{red}{SOLUTION} $
点击查看代码
void solve() {
int n, k; cin >> n >> k;
string s; cin >> s;
vector<int> a(n);
if(!(k & 1)) {
for(int i = 0; i < n && k; i ++ ) if(s[i] == '0') {
a[i] = 1;
k --;
}
a[n - 1] += k;
for(int i = 0; i < n; i ++ ) if(a[i] & 1) {
s[i] = s[i] ^ '1' ^ '0';
}
cout << s << "\n";
for(int i = 0; i < n; i ++ ) {
cout << a[i] << " \n"[i == n - 1];
}
} else {
for(int i = 0; i < n && k; i ++ ) if(s[i] == '1') {
a[i] = 1;
k --;
}
a[n - 1] += k;
for(int i = 0; i < n; i ++ ) if(!(a[i] & 1)) {
s[i] = s[i] ^ '1' ^ '0';
}
cout << s << "\n";
for(int i = 0; i < n; i ++ ) {
cout << a[i] << " \n"[i == n - 1];
}
}
}
C. Line Empire
题意
有一个国王处于一维坐标轴的原点处,他想征服处于正半轴的其他国家,每次攻打只可以攻打距离他最近的没有攻打过的国家,国王也可以选择移动到附近的国家。
假设国王处于 \(c_1\),攻打的国家处于 \(c_2\),那么征服的代价是 \(a \times |c_1 - c_2|\)
假设国王处于 \(c_1\),移动的国家处于 \(c_2\),那么移动的代价是 \(b \times |c_1 - c_2|\)
注:国王只可以移动到已经被征服的国家 , 国王只可以征服距离他最近的没有被征服的国家,即不可以隔山打牛
思路
经过观察我们可以发现,我们移动的最终点只有一个(好像说了一句废话)
我们可以枚举我们要移动的终点,我们计算出从起点到这个终点的代价,以及从这个移动的终点一直到最后一个国家的代价,找到最小值即是答案。
$\color{red}{SOLUTION} $
点击查看代码
void solve() {
int n, a, b; cin >> n >> a >> b;
vector<int> x(n + 1);
for(int i = 1; i <= n; i ++ ) cin >> x[i];
vector<ll> f1(n + 1), f2(n + 1), f(n + 1);
//起点到这个终点的代价
for(int i = n - 1; i >= 0; i -- ) {
f1[i] = f1[i + 1] + (ll)(x[i + 1] - x[i]) * (n - i);
}
//这个移动的终点一直到最后一个国家的代价
for(int i = 1; i <= n; i ++ ) {
f2[i] = f2[i - 1] + (ll)(x[i] - x[i - 1]);
f[i] = (ll)a * (x[i] - x[0]);
}
ll ans = INT64_MAX;
//我计算的是 b 的个数,因此要乘以一个 b
for(int i = 0; i <= n; i ++ ) {
ans = min(ans, f1[i] * b + f2[i] * b + f[i]);
}
cout << ans << "\n";
}
D. Reverse Sort Sum
题意
定义 \(f(k,A)\) 表示对数组 \(A\) 的前 \(k\) 个数字排序得到的新数组
\(A\) 是一个包含 \(0\) 和 \(1\) 的数组
数组 \(C\) 是 $f(1, A) + f(2,A) + ... f(n,A) $ 的对应位置的元素和
给定数组 \(C\),求出原始数组 \(A\)
思路
经过倒序来判断最后一位是否是 \(1\),即可求出最后的答案,我们需要的操作就是区间修改以及单点求值
那么我们可以维护一个差分树状数组来实现这一操作
我们在第 \(i\) 位 减去的贡献就是 把 \([i-cnt+1, i]\) 这个区间的数字都减去 \(1\), \(cnt\) 是当前还剩的 \(1\) 的个数
原因是 \(f(k,A)\) 是有序的,因此他的所有 \(1\) 都在最后面。
$\color{red}{SOLUTION} $
点击查看代码
struct Bit {
int n;
vector<int> tr;
Bit() {};
Bit(int _n) { n = _n; tr.resize(n + 1); };
void add(int a, int b) {
while(a <= n) tr[a] += b, a += a & -a;
}
int sum(int a, int ret = 0) {
while(a > 0) ret += tr[a], a -= a & -a;
return ret;
}
};
void solve() {
int n; cin >> n;
vector<int> a(n), ans(n);
Bit f(n);
for(int i = 0; i < n; i ++ ) {
cin >> a[i];
if(!i)
f.add(i + 1, a[i]);
else
f.add(i + 1, a[i] - a[i - 1]);
}
int cnt = accumulate(a.begin(), a.end(), 0ll) / n;
for(int i = n - 1; i >= 0; i -- ) {
int now = i - cnt + 2;
if(f.sum(i + 1) == i + 1) {
ans[i] = 1;
cnt --;
}
f.add(now, -1);
}
for(int i = 0; i < n; i ++ ) {
cout << ans[i] << " \n"[i == n - 1];
}
}