THUPC2018 城市地铁规划
THUPC2018 城市地铁规划
本题解和别的题解有两个不同。一个是 DP 采用了决策单调性,复杂度 ,劣于别人的 。另一个是根据度数构造树采用 set
,,也劣于别人的 ,但是能过。
题目大意
便于写代码,记号有改动。
给 个点,每个点 的权值和它的度 有关,为 。我们需要给这些点之间连尽可能少的边,使整个图连通,并且使所有点权和尽可能大。
题目分析
显然这个题需要连 条边,连成一棵树。每连一条边,都可以给点的度数总和增加 ,树有 条边, 所以总度数是 。首先所有点的度数至少是 ,这样我们需要把 个度分给 个点,然后 DP 求出最大的权值。
假设已知度数
假设已经求出了每个点度数大于等于 的度数分配。我们一定可以通过下面的方法构造出合法的连边方式。
对于 的情况,无需连边,注意特判。对于 的情况,直接连接两个点即可得到正确连边构造。
主要考虑 的情况。因为一个合法的分配是 个点加 个度,只要 ,一定有 。根据抽屉原理一定存在度数大于 的点。然后知道任何情况下 ,因此一定存在度数小于 的点。规定了分配中每个点度数最小为 ,所以也就是度数为 的点一定存在。
找出任意度数大于 的点 ,假设它的度为 。那么这个时候任选一个度数为 的点和自己相连,两个点就合并成了一个度数为 的新点。这样就把原问题转化为一个仍然具备上面性质的新的子问题。因为每次 都会减少 ,所以 轮后会得到 的问题,这是问题的边界,只要连接这两个点即可完成构造。
我是用 set
查找和维护点和它们的度数,如果得到的度数序列单调 (我们确实实现了所求的度数序列单调),也可以用双指针实现线性构造。但是因为 DP 的复杂度是瓶颈,所以这个优化无所谓有或没有。
求度数
前面没有说如何 DP,设计状态 表示决定了 个点,可分配的 个度已经分配了 个,前 个点的权值和的最大值。转移就是枚举这个点选多少个度。状态 ,转移 ,复杂度 。
但是每个点本质相同,我们这样会导致重复的情况被考虑。我们强制要求每个点的度数不比上一个点大,也不会漏掉想要的情况,只要记录每个状态的决策 ,我们就能减少很多的枚举。何况这个题目本来就需要我们记录决策。
复杂度分析
经过推导证明,这样的转移复杂度总和应该是 的。我每次转移完 ,下一次的转移需要枚举的 的范围就变成了 。
因为规定了决策单调不增,所以对于 转移到别的状态的情况,它的决策不会超过 。因此需要枚举 的数量 也不会超过 。对于 等于 的情况,这个数字是 。所以所有 的状态的转移总复杂度就是:
然后看了几篇题解发现这个题就是一个完全背包,直接 就能做。
代码实现
代码省略了缺省源。
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;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· .NET10 - 预览版1新功能体验(一)