20201002
题目
T1
二分答案。 check
的时候对每个路灯进行如下判断
-
如果上一个开着的路灯到该路灯的距离小于等于 \(mid\) 该路灯不用开。
-
如果上一个开着的路灯到该路灯的距离大于 \(mid\),并且该路灯右边 \(mid\) 范围内有路灯该路灯也不用开。
-
如果上一个开着的路灯到该路灯的距离大于 \(mid\),并且该路灯右边 \(mid\) 范围内没有路灯该路灯必须开。
Code:
#include <cstdio>
#include <cstring>
#include <string>
#include <iostream>
#include <algorithm>
#define M 100002
int n, k, a[M];
bool check(int x, int tot = 0) {
int last = -1100000000;
for (int i = 1; i < n; ++i) {
if (a[i] - last > x) {
int p = std::lower_bound(a + 1, a + n + 1, a[i] + x) - a;
if (p == n + 1) {
++tot;
last = a[n];
break;
}
if (a[p] - a[i] > x) --p;
++tot;
last = a[p];
i = p;
}
}
if (a[n] - last > x) ++tot;
return tot <= k;
}
int main() {
scanf("%d %d", &n, &k);
for (int i = 1; i <= n; ++i) scanf("%d", &a[i]);
int l = 0, r = 1000000001, ans = -1;
while (l <= r) {
int mid = (l + r) >> 1;
if (check(mid)) r = mid - 1, ans = mid;
else l = mid + 1;
}
std::cout << ans << '\n';
return 0;
}
T2
找规律。看下面一个例子
\(\begin{aligned}1\ &2\ 3\ 4\ 5 \\ &2\ 1\ 4\ 3\ 5 \\ & \ \ \ 1 \ 4 \ 2 \ 5 \ 3 \\ & \ \ \ \ \ \ 4 \ 2 \ 5 \ 1 \ 3 \\ & \ \ \ \ \ \ \ \ \ 2 \ 5 \ 1 \ 3 \ 4\end{aligned}\)
每操作一次之后将整个序列向后平移一位,可以发现平移之后一个块中被修改的地方到了下一个块中被修改的地方,对这个进行模拟。
时间复杂度\(O(n/2+n/3+\dots+n/n)\)。
Code:
#include <cstdio>
#include <cstring>
#include <string>
#include <iostream>
#include <algorithm>
#define M 2000001
int n, a[M];
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; ++i) a[i] = i;
for (int k = 2; k <= n; ++k) {
int num = n / k;
if (num * k != n) ++num;
a[n + k - 2 + 1] = a[k - 2 + (num - 1) * k + 1];
for (int i = num - 1; i >= 1; --i) {
a[k - 2 + i * k + 1] = a[k - 2 + (i - 1) * k + 1];
}
}
for (int i = n; i <= 2 * n - 1; ++i) printf("%d ", a[i]);
return 0;
}
T3
Code:
前 \(i\) 盏灯,一共开了 \(j\) 盏,并且第 \(i\) 盏灯是开着的,这时前 \(i\) 盏灯的最小的距离之和为 \(f_{i,j}\)。
\([i,j]\) 中只开了 \(i\) 和 \(j\) 两盏灯时,\([i,j]\) 的最小距离和为 \(g_{i,j}\),可以预处理。
转移的时候枚举再开开第几个灯 \(l\),上一个开着的灯 \(i\),这次开的灯 \(j\),则有状态转移方程 f[j][l] = min(f[j][l], f[i][l - 1] + g[i][j]
。
#include <cstdio>
#include <cstring>
#include <string>
#include <iostream>
#include <algorithm>
#define M 201
#define inf 998244353
int min(int a, int b) { return a < b ? a : b; }
int n, k, a[M];
int g[M][M], f[M][M];
int main() {
scanf("%d %d", &n, &k), a[0] = -inf;
for (int i = 1; i <= n; ++i) scanf("%d", &a[i]);
for (int i = 1; i <= n; ++i) {
for (int j = i + 1; j <= n; ++j) {
for (int p = i + 1; p < j; ++p) {
g[i][j] += min(a[p] - a[i], a[j] - a[p]);
}
}
}
for (int i = 1; i <= n; ++i) {
for (int j = 1; j < i; ++j) {
g[0][i] += a[i] - a[j];
}
}
memset(f, 0x3f, sizeof f);
for (int i = 0; i <= n; ++i) f[i][1] = g[0][i];
for (int l = 2; l <= k; ++l) {
for (int i = l - 1; i <= n; ++i) {
for (int j = i + 1; j <= n; ++j) {
f[j][l] = min(f[j][l], f[i][l - 1] + g[i][j]);
}
}
}
int ans = inf;
for (int i = k; i <= n; ++i) {
int tot = 0;
for (int j = i + 1; j <= n; ++j) tot += a[j] - a[i];
ans = min(ans, f[i][k] + tot);
}
std::cout << ans << '\n';
return 0;
}
T4
每一个组合中的三个点都是到同一个中间点距离为 \(2\) 的点,考虑枚举中间点去计算答案。
\(f_i\) 表示 \(i\) 号的儿子的点权和,一次 \(dfs\) 维护。
如果当前在点 \(u\),答案由以下几部分组成:
1.\(u\) 的爷爷与 \(u\) 的孙子。
2.\(u\) 的兄弟(同父)与 \(u\) 的孙子。
3.\(u\) 的孙子之间。
枚举 \(u\) 的儿子结点的时候有一个顺序,已经枚举完了的点都可以看做是 \(u\) 的爷爷,三部分就都变成了第一部分,用 \(sum\) 去维护已知的爷爷的权值和,用 \(prod\) 去维护已知的爷爷间两两的乘积,\(ans\) 去维护答案。
每到一个点 \(v\) 的时候就
ans += prod * f[v];
prod += sum * f[v];
sum += f[v];
Code:
#include <cstdio>
#include <cstring>
#include <string>
#include <iostream>
#include <algorithm>
#define M 100001
typedef long long ll;
int n, pthn, a[M], head[M];
ll ans, f[M];
struct Edge {
int next, to;
}pth[M << 1];
void add(int from, int to) {
pth[++pthn].to = to, pth[pthn].next = head[from];
head[from] = pthn;
}
void dfs(int u, int fa) {
for (int i = head[u]; i; i = pth[i].next) {
int v = pth[i].to;
if (v != fa) {
dfs(v, u);
f[u] += a[v];
}
}
}
void solve(int u, int father, int grandpa) {
ll sum = 0, prod = 0;
if (father) sum += f[father] - a[u];
if (grandpa) sum += a[grandpa];
for (int i = head[u]; i; i = pth[i].next) {
int v = pth[i].to;
if (v != father) {
solve(v, u, father);
ans += prod * f[v];
prod += sum * f[v];
sum += f[v];
}
}
}
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; ++i) scanf("%d", &a[i]);
for (int i = 1, u, v; i < n; ++i) {
scanf("%d %d", &u, &v);
add(u, v), add(v, u);
}
dfs(1, 0), solve(1, 0, 0);
std::cout << ans << '\n';
return 0;
}