如何做呢?考虑 dp 解决这个问题,令 f[i][S] 为以 i 为根节点的树子树中包括了 S 这个关键点集合的最小代价。
我们一共有两种转移:
对于一个点 i 它所包含的一个集合 S ,有转移 fi,S=maxT⊆S{fi,T+fi,S−T} 。
对于每个集合 S ,把 fi,S<∞ 的点 i 放入队列中,跑关于 S 这个状态的 Spfa 。因为此处的 dp 转移其实就是松弛操作。
然后假设整张图有 n 个点 m 条边,有 p 个关键点。
复杂度是 O(n×3p+km×2p) 。其中 k 是 Spfa 那个系数,可以被卡到 O(n) 。。。
那么对于这道题,我们考虑直接对于每种频道的做一个斯坦纳树。然后令 g[S] 为 S 这个集合的最优答案,初值为 minfi,S 然后直接做一遍子集 dp 就好啦。
总结
多学冷门数据结构、算法哇。。。裸题就十分地好做QAQ
代码
#include<bits/stdc++.h>#define For(i, l, r) for (register int i = (l), i##end = (int)(r); i <= i##end; ++i)#define Fordown(i, r, l) for (register int i = (r), i##end = (int)(l); i >= i##end; --i)#define Rep(i, r) for (register int i = (0), i##end = (int)(r); i < i##end; ++i)#define Set(a, v) memset(a, v, sizeof(a))#define Cpy(a, b) memcpy(a, b, sizeof(a))#define debug(x) cout << #x << ": " << (x) << endlusingnamespace std;
template<typename T> inlineboolchkmin(T &a, T b){ return b < a ? a = b, 1 : 0; }
template<typename T> inlineboolchkmax(T &a, T b){ return b > a ? a = b, 1 : 0; }
inlineintread(){
intx(0), sgn(1); charch(getchar());
for (; !isdigit(ch); ch = getchar()) if (ch == '-') sgn = -1;
for (; isdigit(ch); ch = getchar()) x = (x * 10) + (ch ^ 48);
return x * sgn;
}
voidFile(){
#ifdef zjp_shadowfreopen ("2110.in", "r", stdin);
freopen ("2110.out", "w", stdout);
#endif}
constint N = 1025, M = 3010 * 2, inf = 0x3f3f3f3f;
int n, m, p;
int Head[N], Next[M], to[M], val[M], e = 0;
inlinevoidadd_edge(int u, int v, int w){
to[++ e] = v; Next[e] = Head[u]; val[e] = w; Head[u] = e;
}
int maxsta;
namespace Steiner_Tree {
int f[N][N]; bitset<N> inq;
queue<int> Q;
voidSpfa(int sta){
while (!Q.empty()) {
int u = Q.front(); Q.pop(); inq[u] = false;
for (int i = Head[u], v = to[i]; i; v = to[i = Next[i]]) {
if (chkmin(f[v][sta], f[u][sta] + val[i]))
if(!inq[v]) Q.push(v), inq[v] = true;
}
}
}
voidBuild(){
For (S, 1, maxsta) {
For (i, 1, n) {
for (int T = (S - 1) & S; T; T = (T - 1) & S)
chkmin(f[i][S], f[i][T] + f[i][S ^ T]);
if (f[i][S] < inf) Q.push(i), inq[i] = true;
}
Spfa(S);
}
}
};
int c[N], d[N], g[N], con[N];
inlineboolCheck(int sta){
For (i, 1, p) {
int cur = sta & con[i];
if (cur && cur != con[i]) returnfalse;
}
returntrue;
}
intmain(){
File();
n = read(); m = read(); p = read();
while (m --) {
int u = read(), v = read(), w = read();
add_edge(u, v, w); add_edge(v, u, w);
}
For (i, 1, p) c[i] = read(), d[i] = read();
For (i, 1, p) For (j, 1, p)
if (c[i] == c[j]) con[i] |= (1 << (j - 1));
maxsta = (1 << p) - 1;
using Steiner_Tree :: f;
Set(f, 0x3f);
For (i, 1, p)
f[d[i]][1 << (i - 1)] = 0;
Steiner_Tree :: Build();
Set(g, 0x3f);
For (S, 1, maxsta) if (Check(S)) {
For (i, 1, n) chkmin(g[S], f[i][S]);
for (int T = S - 1; T; T = (T - 1) & S)
chkmin(g[S], g[T] + g[S ^ T]);
}
printf ("%d\n", g[maxsta]);
return0;
}
「JLOI2015」战争调度
题意
王国的公民关系刚好组成一个 n 层的完全二叉树。公民 i 的下属是 2i 和 2i+1。最下层的公民即叶子节点的公民是平民,平民没有下属,最上层的是国王,中间是各级贵族。
现在这个王国爆发了战争,国王需要决定每一个平民是去种地以供应粮食还是参加战争,每一个贵族(包括国王自己)是去管理后勤还是领兵打仗。一个平民会对他的所有直系上司有贡献度,若一个平民 i 参加战争,他的某个直系上司 j 领兵打仗,那么这个平民对上司的作战贡献度为 wij。若一个平民 i 种地,他的某个直系上司 j 管理后勤,那么这个平民对上司的后勤贡献度为 fij,若 i 和 j 所参加的事务不同,则没有贡献度。为了战争需要保障后勤,国王还要求不多于 m 个平民参加战争。
国王想要使整个王国所有贵族得到的贡献度最大,请求出这个值。
2≤n≤10,m≤2n−1,0≤wij,fij≤2000
题解
看到完全二叉树,自然要想到关于深度的 dp 。
我想到了 HNOI2018 D2 T3 那个简单 dp ,你考虑预留祖先就好了。
具体来说,就是令 fi,j,S 为到第 i 个点,选了 j 个去打仗, i 的祖先的状态为 S 的最大贡献度。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】