QOJ 7373 聚类
本题的 checker 似乎有点问题,我瞪了一下错误信息发现其 ans 小的离谱肯定是错的,我的做法和代码应该是对的。
首先因为 4 个 Sub 都相对比较割裂,于是考虑挨个求解。
Sub 1
因为 \(n\) 很小,所以可以直接考虑子集 dp。
具体来说,记 \(f_s\) 表示已经为 \(s\) 中的元素分好组的最小代价。
转移可以考虑枚举新的一组 \(t\):\(f_s = \max\{g_t, f_{s - t}\}\),其中 \(g_t\) 代表把 \(t\) 中的元素分在一组的 \(\max d\) 的最小值。
\(f\) 可以在 \(\mathcal{O}(3^n)\) 的复杂度内求出,\(g\) 可以在 \(\mathcal{O}(2^nn)\) 的复杂度内求出。
Sub 2
因为数据生成方式就是基于 \(a\) 得出的,所以考虑 \({a}\) 对于这个问题的特殊性。
于是首先考虑,知道了 \(a\) 后该怎么做。
此时再来考虑这个 \(\max d\) 的值,发现其实就是 \(\max\{a_i - \min_{j\in S} a_j, \max_{j\in S} - a_i\}\)。
发现这个式子 \(a_i\) 是个变量暂时不管,最关键的就是 \({\min_{j \in S} a_j, \max_{j\in S} a_j}\) 这两个元素。
于是考虑如果固定下 \(x = \min_{j\in S}, y = \max_{j\in S}\),那么 \(a_i\in [x, y]\) 的 \(i\) 都选入 \(S\) 一定不亏,因为最后是选最小的 \(\max d\) 这部分能最小也能尽可能满足个数 \(\ge k\) 的限制。
于是可以知道的是,选出来的每一组一定都是 \(a\) 排序后的一个区间。
于是可以设 \(f_i\) 为已经为 \(a\) 排序后的 \(1\sim i\) 个元素分配好组的最小代价。
转移可以考虑枚举下一段 \([i + 1, j]\),把代价传给 \(f_{j}\)。
中途需要预处理 \(g_{l, r}\) 表示区间 \([l, r]\) 分到一组的最小代价。
\(f\) 的转移是 \(\mathcal{O}(n^2)\) 的,\(g\) 的预处理可以做到 \(\mathcal{O}(n^2)\)(固定 \(l\),往右扫 \(r\) 的同时动态维护取到最小值的指针),代码实现是 \(\mathcal{O}(n^3)\) 的。
于是接下来考虑如何得到 \(a\) 数组。
考虑到给出的边权是 \(n^2\) 的,于是考虑利用已有信息简化信息。
因为上面的 \(a\) 就需要按照值来排序,所以一个想法就是直接找到排序后的这一条链。
这样的好处是确定链头后就可以根据边权得到一整条链。
于是接下来的问题是如何缩短成这一条链。
但判断是否在链上并不是很好做,于是考虑判断是否不在链上。
考虑不在链上的边,那么其必能找到至少一个中转点使得经过这个中间点的路径权值与其相同。
即对于不在链上的边 \((i, j)\),必然能找到至少一个 \(k\) 满足 \(d_{i, j} = d_{i, k} + d_{k, j}\)。
于是删掉这些边,就成了一个链的形态。
因为实际上在求解 \(f, g\) 的时候只关心 \(a\) 的相对大小,所以随意钦定链头即可。
这部分时间复杂度 \(\mathcal{O}(n^3)\)。
需要特判一下 \(n = 1\);对于 \(d_{i, j} = 0\) 的处理应该也要特殊做一下(我的代码好像没处理阿)。
Sub 3
考虑这个 \(k > \frac{n}{3}\) 的含义。
移一下项是 \(\frac{n}{k} < 3\),结合实际意义,也就是说明组数 \({\le 2}\)。
首先如果 \(k > \frac{n}{2}\),就说明只有一组,那么一定得是全集,枚举中心选最小的即可。
接下来考虑有两组该怎么做。
一个想法是枚举两个部分的中心,但是一个很大的问题是并不好划分两个部分,因为答案涉及最优化。
于是考虑用二分转为判定性问题,此时就相当于是只保留下来了部分的边,要找到两个菊花使得总共 \(n\) 个点且每部分点数 \(\ge k\)。
此时考虑其实因为 \(k \le \frac{n}{2}\),所以只要两部分都 \(\ge k\) 且满足并起来为全集就满足。
对应的构造可以先找到两个中心 \(u, v\),然后把只能连向一边的点先连上,剩下的点用来平衡大小(每次连最小的),可以说明这样一定合法。
可以用 bitset 优化做到 \(\mathcal{O}(\frac{n^3}{\omega}\log V)\)。
其实回到开头,枚举中心的做法也是可做的。
只需要根据后面提到的判定条件分成 \(3\) 个限制分别求,最后取 \(\max\)(都要满足)即可。
时间复杂度 \(\mathcal{O}(n^3)\)。
Sub 4
经过 Sub 3 的思考,这个部分 \(k\) 没有限制显然看起来更强了,于是依然先二分答案 \(R\)。
接下来考虑答案限制中的 \(\frac{R}{R^*}\le 2\) 的意义,移项成 \(R \le 2 R^*\)。
考虑这个 \({2}\) 的系数的实际意义,分析最后分的组是菊花的形式,且每条边边权 \(\le R^*\)。
因为 \(R^*\) 已经说明了与边权有关系,所以考虑把这个 \(2\) 的系数与边扯上关系。
那么这就比较明显了,对于一个菊花,任意两点的简单路径不超过 \(2\) 条边。
且这个题保证了边权满足三角形恒等式,这也就说明对于一个菊花,令中心为点 \(x\),另外两点为 \(y, z\),有 \(d(y, z) \le d(y, x) + d(x, z) \le 2R^*\)。
考虑这个的意义是什么。
这说明了此时已经不被局限于要找到菊花的中心了,而是可以任意找菊花中的一个点作为中心,以 \(2R\) 为距离扩散,此时这个原本的菊花一定在其中,且还有可能有更多的点能放入这个菊花。
接下来以 \(2R\) 为扩散距离,随意找一个还没分入菊花的点作为菊花的中心,把其他的没分入菊花且能被扩散到的点都加入菊花。
此时虽然这些菊花可能是不合法的,但来分析一下此时这个图有什么性质。
不妨把一组合法的答案与其对比,根据前面提到的能够知道,在任意一个合法的答案中的任意一个菊花,在新图中作为菊花中心的点至多只有一个。
这个证明是比较简单的,因为此时这个 \(2R\) 的扩散距离一定能够扩散完整个菊花。
其次可能有些菊花没有中心点,但是能够发现此时这些点一定都能去到其他的新菊花。
于是一个很强的结论是,对于最优答案方案中的菊花,如果其中有新菊花的中心点,那么其能扩展到的点一定是这个菊花点集的超集。
于是这说明了,若二分的 \({R}\) 能有解,那么按照上述方法找到的中心点集,一定存在扩展距离为 \({2R}\) 的合法解。
接下来就是考虑如何找到这个合法解了。
那么此时就要满足每个中心点肯定都要匹配上 \(k - 1\) 的非中心点。
于是考虑网络流。
对于一个非中心点 \(x\),连边 \((\operatorname{S}, x, 1)\),对于中心点 \(y\),连边 \((y, \operatorname{T}, k - 1)\)。
且如果中心点 \(y\) 能够扩展到非中心点 \(x\),连边 \((x, y, 1)\)。
那么当跑出来的最大流为 中心点个数 \(\times (k - 1)\) 时合法。
对于构造方案,首先最大流中匹配上的边可以直接保留。
对于剩下的还没连的非中心点,因为此时已经满足条件了,随意连向一个能被扩展到的中心点即可。
直接暴力找增广路跑 flow 就可以做到 \(\mathcal{O}(n^3\log V)\)。
(注:这篇博客说可以直接贪心调整,我感觉不是很正确,还是得找增广路。)
#include<bits/stdc++.h>
constexpr int maxn = 2e2 + 10;
int n, k, sub, d[maxn][maxn];
std::vector<std::vector<int> > ans;
namespace sub1 {
constexpr int N = 15;
int mx[N][1 << N], c[1 << N], f[1 << N], mask[1 << N];
inline void solve() {
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
mx[i][1 << j] = d[i][j];
}
for (int j = 1; j < (1 << n); j++) {
mx[i][j] = std::max(mx[i][j & -j], mx[i][j - (j & -j)]);
}
}
for (int s = 1; s < (1 << n); s++) {
c[s] = __builtin_ctz(s);
for (int i = 0; i < n; i++) {
if (s >> i & 1 && mx[i][s] < mx[c[s]][s]) {
c[s] = i;
}
}
}
memset(f, 0x3f, sizeof(f)), f[0] = 0;
for (int s = 1; s < (1 << n); s++) {
for (int t = s; t; t = (t - 1) & s) {
if (__builtin_popcount(t) < k) continue;
int val = std::max(mx[c[t]][t], f[s ^ t]);
if (val < f[s]) f[s] = val, mask[s] = t;
}
}
for (int s = (1 << n) - 1; s; ) {
int t = mask[s];
std::vector<int> vec;
vec.push_back(c[t]);
for (int i = 0; i < n; i++) {
if ((t >> i & 1) && i != c[t]) {
vec.push_back(i);
}
}
ans.push_back(vec), s ^= t;
}
}
}
namespace sub2 {
std::vector<int> to[maxn];
int a[maxn], p[maxn];
int mn[maxn][maxn], c[maxn][maxn], f[maxn], lst[maxn];
inline void solve() {
if (n == 1) return ans = {{0}}, void();
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (i == j) continue;
bool f = true;
for (int k = 0; k < n; k++) {
f &= k == i || k == j || d[i][k] + d[k][j] != d[i][j];
}
if (f) to[i].push_back(j);
}
}
int hd = 0;
for (int i = 0; i < n; i++) {
if (to[i].size() == 1) hd = i;
}
for (int i = 0, val = 0; ; i++) {
a[i] = val, p[i] = hd;
if (i == n - 1) break;
int nxt = to[hd][0];
to[nxt].erase(std::find(to[nxt].begin(), to[nxt].end(), hd));
val += d[hd][nxt], hd = nxt;
}
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
mn[i][j] = a[j] - a[i], c[i][j] = i;
for (int k = i; k <= j; k++) {
int val = std::max(a[k] - a[i], a[j] - a[k]);
if (val < mn[i][j]) mn[i][j] = val, c[i][j] = k;
}
}
}
memset(f, 0x3f, sizeof(f)), f[0] = 0;
for (int i = 0; i < n; i++) {
for (int j = i + k - 1; j < n; j++) {
int val = std::max(f[i], mn[i][j]);
if (val < f[j + 1]) f[j + 1] = val, lst[j + 1] = i;
}
}
for (int r = n; r > 0; ) {
int l = lst[r];
std::vector<int> vec;
vec.push_back(c[l][r - 1]);
for (int i = l; i < r; i++) {
if (c[l][r - 1] != i) vec.push_back(i);
}
ans.push_back(vec), r = l;
}
for (auto &vec : ans) {
for (int &x : vec) x = p[x];
}
}
}
namespace sub3 {
std::bitset<maxn> b[maxn];
inline bool check(int val, bool cons) {
for (int i = 0; i < n; i++) {
b[i].reset();
for (int j = 0; j < n; j++) {
b[i].set(j, d[i][j] <= val);
}
}
int x = -1, y = -1;
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (i != j && (b[i].count() - b[i][j]) >= k && (b[j].count() - b[j][i]) >= k &&
(b[i] | b[j]).count() == n) {
x = i, y = j;
}
}
}
if (cons == false) return x != -1;
std::vector<int> vecx, vecy;
vecx.push_back(x), vecy.push_back(y);
std::bitset<maxn> bx = b[x] & (~ b[y]), by = b[y] & (~ b[x]);
for (int i = 0; i < n; i++) {
if (i == x || i == y) continue;
if (bx[i]) vecx.push_back(i);
if (by[i]) vecy.push_back(i);
}
std::bitset<maxn> B = b[x] & b[y];
B[x] = B[y] = 0;
for (int i = 0; i < n; i++) {
if (! B[i]) continue;
if (vecx.size() <= vecy.size()) vecx.push_back(i);
else vecy.push_back(i);
}
ans = {vecx, vecy};
return true;
}
inline void solve() {
if (k * 2 > n) {
int mn = 1e9, id = 0;
for (int i = 0; i < n; i++) {
int mx = 0;
for (int j = 0; j < n; j++) {
mx = std::max(mx, d[i][j]);
}
if (mx < mn) mn = mx, id = i;
}
std::vector<int> vec;
vec.push_back(id);
for (int i = 0; i < n; i++) {
if (i != id) vec.push_back(id);
}
ans.push_back(vec);
return ;
}
int l = 0, r = 1e6, val = 1e6;
while (l <= r) {
int mid = l + r >> 1;
if (check(mid, false)) val = mid, r = mid - 1;
else l = mid + 1;
}
check(val, true);
}
}
namespace sub4 {
namespace flow {
int f[maxn][maxn];
inline void init() { memset(f, 0, sizeof(f)); }
inline void add(int x, int y, int z) { f[x][y] = z; }
int pre[maxn];
inline int mxflow() {
int F = 0;
for (; ; ) {
memset(pre, -1, sizeof(pre)), pre[n] = n;
std::queue<int> Q; Q.push(n);
while (! Q.empty()) {
int u = Q.front(); Q.pop();
for (int v = 0; v <= n + 1; v++) {
if (pre[v] == -1 && f[u][v]) {
pre[v] = u, Q.push(v);
}
}
}
if (pre[n + 1] == -1) break;
F++;
for (int u = n + 1; u != n; ) {
int v = pre[u];
f[u][v]++, f[v][u]--;
u = v;
}
}
return F;
}
}
int key[maxn];
std::vector<int> vec[maxn];
inline bool check(int val, int cons) {
val *= 2;
int m = 0;
for (int i = 0; i < n; i++) {
key[i] = 1;
for (int j = 0; j < i; j++) {
key[i] &= key[j] == 0 || d[j][i] > val;
}
m += key[i];
}
flow::init();
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (! key[i] && key[j] && d[i][j] <= val) {
flow::add(i, j, 1);
}
}
}
for (int i = 0; i < n; i++) {
if (! key[i]) flow::add(n, i, 1);
else flow::add(i, n + 1, k - 1);
}
int F = flow::mxflow();
if (cons == false) return F == m * (k - 1);
for (int i = 0; i < n; i++) {
if (key[i]) vec[i].push_back(i);
}
for (int i = 0; i < n; i++) {
if (key[i]) continue;
bool chs = false;
for (int j = 0; j < n; j++) {
if (flow::f[j][i]) {
vec[j].push_back(i), chs = true;
}
}
for (int j = 0; j < n; j++) {
if (d[i][j] <= val && key[j] && chs == false) {
vec[j].push_back(i), chs = true;
}
}
}
for (int i = 0; i < n; i++) {
if (key[i]) ans.push_back(vec[i]);
}
return true;
}
inline void solve() {
int l = 0, r = 1e6, val = 1e6;
while (l <= r) {
int mid = l + r >> 1;
if (check(mid, false)) val = mid, r = mid - 1;
else l = mid + 1;
}
check(val, true);
}
}
int main() {
scanf("%d%d%d", &n, &k, &sub);
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
scanf("%d", &d[i][j]);
}
}
if (sub == 1) sub1::solve();
if (sub == 2) sub2::solve();
if (sub == 3) sub3::solve();
if (sub == 4) sub4::solve();
printf("%zu\n", ans.size());
for (auto vec : ans) {
printf("%zu ", vec.size());
for (int x : vec) printf("%d ", x + 1);
puts("");
}
for (auto vec : ans) printf("%d ", vec[0] + 1);
return 0;
}

浙公网安备 33010602011771号