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;
}

知识点

数论

posted @ 2020-10-23 09:12  yu__xuan  阅读(63)  评论(0编辑  收藏  举报