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\)

\[f_{l,r,i,x,y}\leftarrow f_{l,r,i+1,x,y}+c_i[\lfloor\frac x{2^i}\rfloor\bmod 2\ne\lfloor\frac y{2^i}\rfloor\bmod 2] \]

\(a_{l\dots r}\) 的第 \(i\) 位可以随便取,如果 \(x\)\(y\) 的第 \(i\) 位不同则会产生一次代价。

(2)枚举 \(l\le mid\le r\)\(a_{mid}\) 选择的子区间长度为 \(2^i\),区间里的一个数为 \(z\)

\[f_{l,r,i,x,y}\leftarrow f_{l,mid-1,i,x,z}+f_{mid+1,r,i,z,y} \]

对于 \(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;
}
posted @ 2020-12-02 21:53  epic01  阅读(213)  评论(0编辑  收藏  举报