THUPC2018 城市地铁规划

THUPC2018 城市地铁规划

本题解和别的题解有两个不同。一个是 DP 采用了决策单调性,复杂度 \(O(n^2\ln n)\),劣于别人的 \(O(n^2)\)。另一个是根据度数构造树采用 set\(O(n \log n)\),也劣于别人的 \(O(n)\),但是能过。

传送门

题目大意

便于写代码,记号有改动。

\(n\) 个点,每个点 \(i\) 的权值和它的度 \(De_i\) 有关,为 \(\sum_{j = 0}^{m}a_j{De_i}^j \pmod{59393}\)。我们需要给这些点之间连尽可能少的边,使整个图连通,并且使所有点权和尽可能大。

题目分析

显然这个题需要连 \(n - 1\) 条边,连成一棵树。每连一条边,都可以给点的度数总和增加 \(2\),树有 \(n - 1\) 条边, 所以总度数是 \(2(n - 1)\)。首先所有点的度数至少是 \(1\),这样我们需要把 \(n - 2\) 个度分给 \(n\) 个点,然后 DP 求出最大的权值。

假设已知度数

假设已经求出了每个点度数大于等于 \(1\) 的度数分配。我们一定可以通过下面的方法构造出合法的连边方式。

对于 \(n = 1\) 的情况,无需连边,注意特判。对于 \(n = 2\) 的情况,直接连接两个点即可得到正确连边构造。

主要考虑 \(n > 2\) 的情况。因为一个合法的分配是 \(n\) 个点加 \(2n - 2\) 个度,只要 \(n > 2\),一定有 \(2n - 2 > n\)。根据抽屉原理一定存在度数大于 \(1\) 的点。然后知道任何情况下 \(2n - 2 < 2n\),因此一定存在度数小于 \(2\) 的点。规定了分配中每个点度数最小为 \(1\),所以也就是度数为 \(1\) 的点一定存在。

找出任意度数大于 \(1\) 的点 \(x\),假设它的度为 \(De_x\)。那么这个时候任选一个度数为 \(1\) 的点和自己相连,两个点就合并成了一个度数为 \(De_x - 1\) 的新点。这样就把原问题转化为一个仍然具备上面性质的新的子问题。因为每次 \(n\) 都会减少 \(1\),所以 \(n - 2\) 轮后会得到 \(n' = 2\) 的问题,这是问题的边界,只要连接这两个点即可完成构造。

我是用 set 查找和维护点和它们的度数,如果得到的度数序列单调 (我们确实实现了所求的度数序列单调),也可以用双指针实现线性构造。但是因为 DP 的复杂度是瓶颈,所以这个优化无所谓有或没有。

求度数

前面没有说如何 DP,设计状态 \(f_{i, j}\) 表示决定了 \(i\) 个点,可分配的 \(n - 2\) 个度已经分配了 \(j\) 个,前 \(i\) 个点的权值和的最大值。转移就是枚举这个点选多少个度。状态 \(O(n^2)\),转移 \(O(n)\),复杂度 \(O(n^3)\)

\[f_{i, j} = max_{k = 0}^{j} (f_{i - 1, j - k} + ((\sum_{l = 0}^{m}a_lx^l) \% 59393)) \]

但是每个点本质相同,我们这样会导致重复的情况被考虑。我们强制要求每个点的度数不比上一个点大,也不会漏掉想要的情况,只要记录每个状态的决策 \(Ch_{i, j}\),我们就能减少很多的枚举。何况这个题目本来就需要我们记录决策。

复杂度分析

经过推导证明,这样的转移复杂度总和应该是 \(O(n^2\ln n)\) 的。我每次转移完 \(f_{i, j}\),下一次的转移需要枚举的 \(k\) 的范围就变成了 \([0, min(Ch_{i, j}, n - 2 - j)]\)

因为规定了决策单调不增,所以对于 \(f_{i, j}\) 转移到别的状态的情况,它的决策不会超过 \(\frac {j}{i}\)。因此需要枚举 \(k\) 的数量 \(min(Ch_{i, j}, n - 2 - j)\) 也不会超过 \(\frac {j}{i}\)。对于 \(i\) 等于 \(0\) 的情况,这个数字是 \(n - 2\)。所以所有 \(O(n^2)\) 的状态的转移总复杂度就是:

\[\begin{aligned} & O(n(n - 2) + \sum_{i = 1}^{n}\sum_{j = 0}^{n - 2} \frac{j}{i})\\ =& O(n^2 + \sum_{j = 0}^{n - 2}j\sum_{i = 1}^n\frac 1i)\\ =& O(n^2 + \sum_{j = 0}^{n - 2}\ln n)\\ =& O(n^2 + (n - 2)(n - 1)\ln n)\\ =& O(n^2(1 + \ln n))\\ =& O(n^2\ln n))\\ \end{aligned} \]

然后看了几篇题解发现这个题就是一个完全背包,直接 \(O(n^2)\) 就能做。

代码实现

代码省略了缺省源。

const unsigned Mod(59393);
unsigned a[15], g[3005], f[3005][3005], Ch[3005][3005], m, n;
unsigned Way[3005];
unsigned A, B, C, D, t;
unsigned Cnt(0), Ans(0), Tmp(0);
set<pair<unsigned, unsigned> > S;
signed main() {
  n = RD(), m = RD();
  for (unsigned i(0); i <= m; ++i) a[i] = RD();
  for (unsigned i(0); i <= n; ++i) {
    g[i] = 0;
    for (unsigned j(0), k(1); j <= m; ++j) g[i] = (g[i] + k * a[j]) % Mod, k = k * i % Mod;
  }
  if (n == 1) { printf("%u %u\n", 0, g[0]); return 0; }
  Ch[0][0] = n - 2;
  for (unsigned i(0); i < n; ++i)  for (unsigned j(n - 2); ~j; --j) {
    for (unsigned k(0); (k <= Ch[i][j]) && (j + k <= n - 2); ++k) {
      unsigned Des(f[i][j] + g[k + 1]);
      if (f[i + 1][j + k] < Des) f[i + 1][j + k] = Des, Ch[i + 1][j + k] = k;
    }
  }
  for (unsigned i(n), j(n - 2); i; --i) Way[i] = 1 + Ch[i][j], j -= Ch[i][j];
  printf("%u %u\n", n - 1, f[n][n - 2]);
  for (unsigned i(1); i <= n; ++i) S.insert(make_pair(Way[i], i));
  for (unsigned i(1); i < n; ++i) {
    unsigned Nu((S.rbegin())->second), No((S.rbegin())->first);
    S.erase(make_pair(No, Nu));
    unsigned Num((S.begin())->second), Now((S.begin())->first);
    S.erase(S.begin());
    printf("%u %u\n", Num, Nu);
    if (No > 1) S.insert(make_pair(No - 1, Nu));
  }
  return Wild_Donkey;
}
posted @ 2022-01-11 18:28  Wild_Donkey  阅读(69)  评论(1编辑  收藏  举报