THUPC2018 城市地铁规划

THUPC2018 城市地铁规划

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

传送门

题目大意

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

n 个点,每个点 i 的权值和它的度 Dei 有关,为 j=0majDeij(mod59393)。我们需要给这些点之间连尽可能少的边,使整个图连通,并且使所有点权和尽可能大。

题目分析

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

假设已知度数

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

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

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

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

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

求度数

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

fi,j=maxk=0j(fi1,jk+((l=0malxl)%59393))

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

复杂度分析

经过推导证明,这样的转移复杂度总和应该是 O(n2lnn) 的。我每次转移完 fi,j,下一次的转移需要枚举的 k 的范围就变成了 [0,min(Chi,j,n2j)]

因为规定了决策单调不增,所以对于 fi,j 转移到别的状态的情况,它的决策不会超过 ji。因此需要枚举 k 的数量 min(Chi,j,n2j) 也不会超过 ji。对于 i 等于 0 的情况,这个数字是 n2。所以所有 O(n2) 的状态的转移总复杂度就是:

O(n(n2)+i=1nj=0n2ji)=O(n2+j=0n2ji=1n1i)=O(n2+j=0n2lnn)=O(n2+(n2)(n1)lnn)=O(n2(1+lnn))=O(n2lnn))

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

代码实现

代码省略了缺省源。

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 @   Wild_Donkey  阅读(75)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示