CF1948E 题解

· · 题解

Update on 2024.10.29:修改了一处笔误,感谢 @Bugbread 指出,顺便修改了一些表述。

玄学,赛时乱搞过了。

题意

给你 N 个点与正整数 k,让你给每个点赋一个权值 a_i,使得 \{a_i\} 是一个 1 \sim N 的排列。然后对所有满足 |i - j| + |a_i - a_j| \le k 的数对 (i, j),在结点 i 与结点 j 之间有连一条边。求一个满足条件的 \{a_i\},使图可以分成 M 个团,并最小化 M

思路

以下 i, j 两数若同时出现,若无特殊说明,均为正整数,并满足 1 \le i, j \le N,且 i \not = j

首先两个显然的贪心:

还有两个结论:

综上,可以得出最佳的划分方案:

根据结论四,显然我们可以把图划分为若干部分,使得每个部分可以转化为:结点编号为 1 \sim M\{a_i\}1 \sim M 的排列,构造一个 \{a_i\} 使得这个图按给定方式连边后是一个团。

显然每个部分是否可以通过合适的 \{a_i\} 转化为团,只与其所含结点的数量有关。

根据结论三,不可能有 M > k,考虑构造 M = k 的情况。

可以构造:

\lfloor \frac{M}{2} \rfloor + 1 - i & \text{if } i \le \lfloor \frac{M}{2} \rfloor \\ M + \lfloor \frac{M}{2} \rfloor + 1 - i & \text{otherwise.} \end{cases}

来满足要求(即对于 \{a_i\} = \{1, 2, \dots, M\},反转其区间 [1, \lfloor \frac{M}{2} \rfloor] 与区间 [\lfloor \frac{M}{2} \rfloor + 1, M])。

证明(设 1 \le i < j \le M,且 i, j 是正整数):

证毕。

#include <bits/stdc++.h>
#define ll long long
using namespace std;
ll Read() {
    int sig = 1;
    ll num = 0;
    char c = getchar();
    while(!isdigit(c)) {
        if(c == '-') {
            sig = -1;
        }
        c = getchar();
    }
    while(isdigit(c)) {
        num = (num << 3) + (num << 1) + (c ^ 48);
        c = getchar();
    }
    return num * sig;
}
void Write(ll x) {
    if(x < 0) {
        putchar('-');
        x = -x;
    }
    if(x >= 10) {
        Write(x / 10);
    }
    putchar((x % 10) ^ 48);
}

vector<int> Gen_vec(int siz) {
    int i;
    vector<int> vec;
    for(i = siz / 2; i >= 1; i--) {
        vec.push_back(i);
    }
    for(i = siz; i > siz / 2; i--) {
        vec.push_back(i);
    }
    return vec;
}
int main() {
    int testcase;
    scanf("%d", &testcase);
    while(testcase--) {
        int n, k, i, j;
        n = Read(), k = Read();
        for(i = 1; i <= n; i += k) {
            int l = i, r = min(i + k - 1, n);
            auto vec = Gen_vec(r - l + 1);
            for(auto v : vec) {
                Write(v + i - 1), putchar(' ');
            }
        }
        putchar('\n'), Write((n + k - 1) / k), putchar('\n'); // (n + k - 1) / k 下取整 = n / k 上取整
        for(i = 1; i <= n; i += k) {
            int l = i, r = min(i + k - 1, n);
            for(j = l; j <= r; j++) {
                Write((l - 1) / k + 1), putchar(' ');
            }
        }
        putchar('\n');
    }
    return 0;
}