CF1948E 题解
Update on 2024.10.29:修改了一处笔误,感谢 @Bugbread 指出,顺便修改了一些表述。
玄学,赛时乱搞过了。
题意
给你
思路
以下
首先两个显然的贪心:
-
结论一:所有选择的团内,结点的编号一定连续,即,团内结点的编号的集合类似于
\{l, l + 1, l + 2, \dots, r - 1, r\} 。- 证明:设有两个团,且一个团内结点编号的最大值大于另一个团内结点编号的最小值,即类似的理解为这两个团节点编号在在值域上“相交”,那么可以合理“交换”两个点(即,在交换结点所在的团的同时,交换
a_i ),使得两个团在值域上“不相交”。
例如:两个团的结点编号集合分别为
S_1 = \{1, 3\} 和S_2 = \{2\} ,\{a_i\} = \{1, 2, 3\} ,那么可以交换为S_1' = \{1, 2\} 和S_2 = \{3\} ,\{a_i'\} = \{1, 3, 2\} 。交换后,在不等式
|i - j| + |a_i - a_j| \le k 中,|a_i - a_j| 不变,|i - j| 总是减小。则只会连更多的边,不会影响结果。 - 证明:设有两个团,且一个团内结点编号的最大值大于另一个团内结点编号的最小值,即类似的理解为这两个团节点编号在在值域上“相交”,那么可以合理“交换”两个点(即,在交换结点所在的团的同时,交换
-
结论二:所有选择的团内,所有结点的权值
a_i 排序后一定连续,即,团内结点的权值a_i 的集合类似于\{l, l + 1, l + 2, \dots, r - 1, r\} 。- 证明:将
a_i 看做新的结点编号,结点编号看做新的a_i ,构造新图。则在新图中,对于每个有连边的数对(i, j) ,在新图中(a_i, a_j) 依然是连边的,无连边的数对亦然,余下同结论一证明。
- 证明:将
还有两个结论:
- 结论三:每个团大小不超过
k 。- 证明:若团大小超过
k ,则必有一个两个数均为团内结点编号的数对(i, j) ,使得|i - j| \ge k 。又因为对于任意i,j ,|a_i - a_j| \ge 1 ,所以|i - j| + |a_i - a_j| > k ,不满足团的性质。
- 证明:若团大小超过
- 结论四:对于任意整数
t ,将每个团中各结点权值加上t 不影响结果。- 证明:令
a_i' = a_i + t ,显然对于任意数对(i, j) ,|i - j| 不变,|a_i' - a_j'| = |(a_i + t) - (a_j + t)| = |a_i + t - a_j - t| = |a_i - a_j| ,所以|i - j| + |a_i' - a_j'| = |i - j| + |a_i - a_j| ,不影响结果。
- 证明:令
综上,可以得出最佳的划分方案:
- 将
1 \sim N 个结点划分成t 个部分,使得每个部分内编号连续,每个部分内a_i 的编号连续且其值域与结点编号的值域相等。形式化的,将N 个结点划分为[l_1, l_2), [l_2, l_3), [l_3, l_4), \dots, [l_{t - 1}, l_t), [l_t, l_{t + 1}) 这样t 个部分,使得1 = l_1 < l_2 < l_3 < \dots < l_t < l_{t + 1} = N + 1 ,且\forall i \in [1, t] ,l_i \le a_i < l_{i + 1} 。
根据结论四,显然我们可以把图划分为若干部分,使得每个部分可以转化为:结点编号为
显然每个部分是否可以通过合适的
根据结论三,不可能有
可以构造:
来满足要求(即对于
证明(设
证毕。
#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;
}