Codeforces Round #687 (Div. 1) 简要题解
A
题意
一个长度为 \(n\) 的 \(01\) 字符串,你可以花费 \(x\) 的代价把一个 \(0\) 改成 \(1\),也可以花费 \(y\) 的代价把第一个字符移走。
求让 \(p,p+k,p+2k,\dots\) 位置上的字符全为 \(1\) 的最小代价。
\(1\le p\le n\le 10^5\),\(1\le k\le n\),\(1\le x,y\le 10^4\)。
多测,\(t\le 100\),\(n\) 的总和不超过 \(10^5\)。
Solution
模拟题。算出以每个下标为初始 \(p\) 要改多少个 \(0\) 之后枚举移走的字符个数即可,\(O(\sum n)\)。
Code
#include <bits/stdc++.h>
template <class T>
inline void read(T &res)
{
res = 0; bool bo = 0; char c;
while (((c = getchar()) < '0' || c > '9') && c != '-');
if (c == '-') bo = 1; else res = c - 48;
while ((c = getchar()) >= '0' && c <= '9')
res = (res << 3) + (res << 1) + (c - 48);
if (bo) res = ~res + 1;
}
template <class T>
inline T Min(const T &a, const T &b) {return a < b ? a : b;}
const int N = 1e5 + 5, INF = 0x3f3f3f3f;
int n, p, k, x, y, sum[N];
char s[N];
void work()
{
read(n); read(p); read(k);
scanf("%s", s + 1); read(x); read(y);
int ans = INF;
for (int i = n; i >= 1; i--)
sum[i] = s[i] == '0', sum[i] += i + k <= n ? sum[i + k] : 0;
for (int i = 0; i <= n - p; i++)
ans = Min(ans, y * i + x * sum[i + p]);
printf("%d\n", ans);
}
int main()
{
int T; read(T);
while (T--) work();
return 0;
}
B
题意
一个长度为 \(n\) 的单调不减序列 \(a\),定义一次操作为选定 \(a\) 中两个相邻的数合并,合并之后这个数等于原来两个数的 \(\text{xor}\)。
求让这个序列不再单调不减的最小步数,无解输出 \(-1\)。
\(2\le n\le10^5\),\(1\le a_i\le10^9\)。
Solution
如果有连续的三个数,它们的最高位相同,则把后面两个合并即可,故 \(n\) 大于两倍 \(\log a\) 则答案为 \(1\)。
否则暴力枚举两个相邻的区间,判断这两个区间异或和大小关系即可。
\(O(n+\log^3 a)\)。
Code
#include <bits/stdc++.h>
template <class T>
inline void read(T &res)
{
res = 0; bool bo = 0; char c;
while (((c = getchar()) < '0' || c > '9') && c != '-');
if (c == '-') bo = 1; else res = c - 48;
while ((c = getchar()) >= '0' && c <= '9')
res = (res << 3) + (res << 1) + (c - 48);
if (bo) res = ~res + 1;
}
template <class T>
inline T Min(const T &a, const T &b) {return a < b ? a : b;}
const int N = 1e5 + 5;
int n, a[N], ans = 20050131;
int main()
{
read(n);
for (int i = 1; i <= n; i++) read(a[i]);
if (n > 64) return puts("1"), 0;
for (int i = 1; i <= n; i++) a[i] ^= a[i - 1];
for (int i = 1; i < n; i++)
for (int j = 0; j < i; j++)
for (int k = i + 1; k <= n; k++)
if ((a[i] ^ a[j]) > (a[k] ^ a[i]))
ans = Min(ans, k - j);
return std::cout << (ans == 20050131 ? -1 : ans - 2) << std::endl, 0;
}
C
题意
\(n\) 个 BOSS,你可以用任意顺序击杀,第 \(i\) 个 BOSS 有个属性值 \(c_i\),可正可负。
你有两个参数 \(delta\) 和 \(sum\),击杀第 \(i\) 个 BOSS 之后会令 \(sum+=delta,delta+=c_i\)。
整个过程中你可以随时让你的 \(delta\) 清零,最多清零 \(k\) 次,求击杀所有 \(n\) 个 BOSS 后最大的 \(sum\)。
\(1\le n\le 5\times10^5\),\(0\le k\le 5\times10^5\),\(|c_i|\le 10^6\)。
Solution
答案即为分成 \(k+1\) 个集合,使得所有集合降序排序后的二阶前缀和之和最大。
考虑贪心:一开始有 \(k+1\) 个空集,\(c\) 从大到小,每次都接在 \(c\) 之和最大的集合上面,总复杂度 \(O(n\log n)\)。
证明可以归纳:\(c\) 为正时显然,否则假如已经证明了最后 \(i\) 个 \(c\) 加入时贪心策略满足,考虑如果倒数第 \(i+1\) 个 \(c\) 没有接在最大的集合上面,则倒数第 \(i\) 个 \(c\) 一定会接在最大的集合上面,两者互换可以得到更优答案。
Code
#include <bits/stdc++.h>
template <class T>
inline void read(T &res)
{
res = 0; bool bo = 0; char c;
while (((c = getchar()) < '0' || c > '9') && c != '-');
if (c == '-') bo = 1; else res = c - 48;
while ((c = getchar()) >= '0' && c <= '9')
res = (res << 3) + (res << 1) + (c - 48);
if (bo) res = ~res + 1;
}
typedef long long ll;
const int N = 5e5 + 5;
int n, k, c[N];
ll ans;
std::priority_queue<ll> pq;
int main()
{
read(n); read(k);
for (int i = 1; i <= n; i++) read(c[i]);
std::sort(c + 1, c + n + 1);
for (int i = 1; i <= k + 1; i++) pq.push(0);
for (int i = n; i >= 1; i--)
{
ll num = pq.top(); pq.pop();
ans += num; pq.push(num + c[i]);
}
return std::cout << ans << std::endl, 0;
}
D
题意
有 \(n\) 个物品,第 \(i\) 个物品在 \(t_i\) 时刻于数轴上点 \(x_i\) 出现,保证 \(t_i\) 严格递增,\(x_i\) 互不相同。
初始 \(0\) 时刻你在位置 \(0\),每个单位时间可以移动一步或者不移动。
当且仅当恰好时刻 \(t_i\) 恰好在位置 \(x_i\) 时,你才能拿走第 \(i\) 个物品,拿走物品用的时间为 \(0\)。
任何时候,你也可以花费 \(0\) 单位时间在当前位置放一个雕塑,雕塑同样可以在特定时间拿走当前位置上的物品,但是在一个新的位置上放雕塑的时候,原来的雕塑会被销毁。
特别地,如果 \(t\) 时刻原来雕塑在位置 \(x\),并把新雕塑放在了位置 \(y\),则在这个时刻 \(x\) 和 \(y\) 上的物品都会被收集。
判断你是否能拿走所有物品,\(1\le n\le 5000\),\(1\le t_i\le 10^9\),\(|x_i|\le 10^9\)。
Solution
利用任何时候只能有一个雕塑的条件,可以 DP:
\(f_i\) 表示能否到达 \(x_i\) 收集物品;
\(g_i\) 表示能否到达 \(x_{i-1}\) 收集物品(物品 \(i\) 让雕塑收集);
\(h_i\) 表示在 \(x_i\) 放上雕塑(必须在时间 \(t_{i-1}\) 之后)的最早时间。
转移基本上就是分当前点到达下一个点,以及对后面的一个物品所在位置提前放雕塑两种情况讨论,\(O(n^2)\)。
Code
#include <bits/stdc++.h>
template <class T>
inline void read(T &res)
{
res = 0; bool bo = 0; char c;
while (((c = getchar()) < '0' || c > '9') && c != '-');
if (c == '-') bo = 1; else res = c - 48;
while ((c = getchar()) >= '0' && c <= '9')
res = (res << 3) + (res << 1) + (c - 48);
if (bo) res = ~res + 1;
}
template <class T>
inline T Abs(T x) {return x < 0 ? -x : x;}
template <class T>
inline T Min(const T &a, const T &b) {return a < b ? a : b;}
template <class T>
inline T Max(const T &a, const T &b) {return a > b ? a : b;}
const int N = 5005;
int n, t[N], x[N], h[N];
bool a[N][N], f[N], g[N];
bool judge(int i, int ti, int j, int k, int m)
{
if (Abs(x[i] - x[k]) > 1000000000) return 0;
int tk = Abs(x[i] - x[k]) + ti; if (tk < m) tk = m;
return tk <= t[j] && t[j] - tk >= Abs(x[k] - x[j]);
}
int main()
{
read(n); f[0] = 1;
for (int i = 0; i <= n; i++) h[i] = 1061109567;
for (int i = 1; i <= n; i++) read(t[i]), read(x[i]), a[i][i] = 1;
for (int i = 1; i <= n; i++)
for (int j = i + 1; j <= n; j++)
a[i][j] = a[i][j - 1] && t[j] - t[j - 1] >= Abs(x[j] - x[j - 1]);
for (int i = 0; i < n; i++)
{
if (f[i])
{
if (t[i + 1] - t[i] >= Abs(x[i + 1] - x[i])) f[i + 1] = 1,
h[i + 1] = Min(h[i + 1], t[i] + Abs(x[i + 1] - x[i]));
for (int j = i + 2; j <= n; j++)
if (a[i + 1][j - 1] && judge(i, t[i], i + 1, j, t[i]))
g[j] = 1;
}
if (g[i])
{
if (t[i + 1] - t[i - 1] >= Abs(x[i + 1] - x[i - 1]))
f[i + 1] = 1, h[i + 1] = Min(h[i + 1], Max(t[i],
t[i - 1] + Abs(x[i + 1] - x[i - 1])));
for (int j = i + 2; j <= n; j++)
if (a[i + 1][j - 1] && judge(i - 1, t[i - 1], i + 1, j, t[i]))
g[j] = 1;
}
if (h[i] < 1061109567)
{
if (t[i + 1] - h[i] >= Abs(x[i + 1] - x[i])) f[i + 1] = 1,
h[i + 1] = Min(h[i + 1], Max(t[i], h[i] + Abs(x[i + 1] - x[i])));
for (int j = i + 2; j <= n; j++)
if (a[i + 1][j - 1] && judge(i, h[i], i + 1, j, t[i]))
g[j] = 1;
}
}
return puts(f[n] || g[n] ? "YES" : "NO"), 0;
}
E
题意
对于一个 \(k\) 位二进制数 \(x\),定义 \(p(x)\) 表示 \(\sum_{i=0}^{k-1}c_i[x的第i位为1]\),\(x\) 的第 \(i\) 位定义为 \(\lfloor\frac x{2^i}\rfloor\bmod 2\)。
有 \(n\) 个数 \(a_{1\dots n}\),满足 \(a_i\in[l_i,r_i]\),求 \(\sum_{i=1}^{n-1}p(a_i\oplus a_{i+1})\) 的最小值,\(\oplus\) 为异或运算。
\(2\le n\le 50\),\(1\le k\le 50\),\(0\le l_i\le r_i<2^k\),\(0\le c_i\le 10^{12}\)。
Solution
考虑把所有的区间在值域为 \([0,2^k)\) 的线段树上拆成 \(O(k)\) 个区间,不难发现:
定义线段树根的深度为 \(0\),则线段树上任何一个第 \(i\) 层的节点,对应的区间都表示最高 \(i\) 位固定,最低 \(k-i\) 位任意。
拆完区间之后,问题可以视为对每个数 \(a_i\) 都选择一个它拆出的子区间计算最优答案。
利用笛卡尔树的思想,可以得到一个区间 DP:\(f_{l,r,i,x,y}\) 表示就 \(\ge i\) 的位,\(a_{l\dots r}\) 的所有数只能选择长度大于等于 \(2^i\) 的子区间,\(a_{l-1}\) 和 \(a_{r+1}\) 分别为 \(x\) 和 \(y\)(我们只需关心 \(x\) 和 \(y\) 的第 \(i\) 到第 \(k-1\) 位)的最优答案。
边界为 \(f_{l,l-1,k,*,*}=0\)。
(1)如果 \(a_{l\dots r}\) 选择的子区间长度都大于 \(2^i\):
即 \(a_{l\dots r}\) 的第 \(i\) 位可以随便取,如果 \(x\) 和 \(y\) 的第 \(i\) 位不同则会产生一次代价。
(2)枚举 \(l\le mid\le r\),\(a_{mid}\) 选择的子区间长度为 \(2^i\),区间里的一个数为 \(z\):
对于 \(l=1\) 或 \(r=n\) 的情况需要一些特殊处理,推荐使用记忆化搜索。
根据线段树的性质,线段树提取区间时在一层内经过的节点数 \(\le 4\),故对于指定的 \(l,r,i\),本质不同的 \(x\) 和 \(y\) 分别都只有 \(4\) 种。
总复杂度 \(O(n^4)\)。
Code
#include <bits/stdc++.h>
template <class T>
inline void read(T &res)
{
res = 0; bool bo = 0; char c;
while (((c = getchar()) < '0' || c > '9') && c != '-');
if (c == '-') bo = 1; else res = c - 48;
while ((c = getchar()) >= '0' && c <= '9')
res = (res << 3) + (res << 1) + (c - 48);
if (bo) res = ~res + 1;
}
typedef long long ll;
const int N = 55;
const ll INF = 1e18;
int n, k, w[N][N];
ll c[N], f[N][N][N][4][4];
std::vector<int> fa[N][N];
std::vector<bool> is[N][N];
std::vector<ll> a[N][N];
void orz(ll l, ll r, ll s, ll e, int i, int j, int f)
{
if (e < l || s > r) return;
a[i][j].push_back(l); fa[i][j].push_back(f); w[i][j]++;
if (s <= l && r <= e) return (void) is[i][j].push_back(1);
else is[i][j].push_back(0);
ll mid = l + r >> 1; int tmp = w[i][j] - 1;
orz(l, mid, s, e, i, j - 1, tmp);
orz(mid + 1, r, s, e, i, j - 1, tmp);
}
ll dp(int l, int r, int num, int x, int y)
{
if (num == k && l > r) return 0;
if (f[l][r][num][x][y] != -1) return f[l][r][num][x][y];
ll res = num < k ? dp(l, r, num + 1,
fa[l - 1][num][x], fa[r + 1][num][y]) : INF;
if (res < INF && l > 1 && r < n && ((a[l - 1][num][x] >> num) & 1)
!= ((a[r + 1][num][y] >> num) & 1)) res += c[num];
for (int i = l; i <= r; i++) for (int j = 0; j < w[i][num]; j++)
{
if (!is[i][num][j]) continue;
ll lc = dp(l, i - 1, num, x, j), rc = dp(i + 1, r, num, j, y);
if (lc + rc < res) res = lc + rc;
}
return f[l][r][num][x][y] = res;
}
int main()
{
ll l, r;
read(n); read(k);
for (int i = 0; i <= k; i++) fa[0][i].push_back(0),
fa[n + 1][i].push_back(0);
for (int i = 1; i <= n; i++) read(l), read(r),
orz(0, (1ll << k) - 1, l, r, i, k, 0);
for (int i = 0; i < k; i++) read(c[i]);
memset(f, -1, sizeof(f));
return std::cout << dp(1, n, 0, 0, 0) << std::endl, 0;
}