A
题意
一个长度为 (n) 的 (01) 字符串,你可以花费 (x) 的代价把一个 (0) 改成 (1),也可以花费 (y) 的代价把第一个字符移走。
求让 (p,p+k,p+2k,dots) 位置上的字符全为 (1) 的最小代价。
(1le ple nle 10^5),(1le kle n),(1le x,yle 10^4)。
多测,(tle 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
", ans);
}
int main()
{
int T; read(T);
while (T--) work();
return 0;
}
B
题意
一个长度为 (n) 的单调不减序列 (a),定义一次操作为选定 (a) 中两个相邻的数合并,合并之后这个数等于原来两个数的 ( ext{xor})。
求让这个序列不再单调不减的最小步数,无解输出 (-1)。
(2le nle10^5),(1le a_ile10^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)。
(1le nle 5 imes10^5),(0le kle 5 imes10^5),(|c_i|le 10^6)。
Solution
答案即为分成 (k+1) 个集合,使得所有集合降序排序后的二阶前缀和之和最大。
考虑贪心:一开始有 (k+1) 个空集,(c) 从大到小,每次都接在 (c) 之和最大的集合上面,总复杂度 (O(nlog 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) 上的物品都会被收集。
判断你是否能拿走所有物品,(1le nle 5000),(1le t_ile 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) 位定义为 (lfloorfrac x{2^i} floormod 2)。
有 (n) 个数 (a_{1dots n}),满足 (a_iin[l_i,r_i]),求 (sum_{i=1}^{n-1}p(a_ioplus a_{i+1})) 的最小值,(oplus) 为异或运算。
(2le nle 50),(1le kle 50),(0le l_ile r_i<2^k),(0le c_ile 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_{ldots r}) 的所有数只能选择长度大于等于 (2^i) 的子区间,(a_{l-1}) 和 (a_{r+1}) 分别为 (x) 和 (y)(我们只需关心 (x) 和 (y) 的第 (i) 到第 (k-1) 位)的最优答案。
边界为 (f_{l,l-1,k,*,*}=0)。
(1)如果 (a_{ldots r}) 选择的子区间长度都大于 (2^i):
即 (a_{ldots r}) 的第 (i) 位可以随便取,如果 (x) 和 (y) 的第 (i) 位不同则会产生一次代价。
(2)枚举 (lle midle 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;
}