最小斯坦纳树
1.1 最小斯坦纳树模型
是一个经典模型:给定一张图和图上的一些关键点,图上边有非负边权,要求选择一些边使得所有关键点联通并且边权和最小。
关键点数一般很小,假设点数、边数、关键点数分别为 ,那么算法的时间复杂度是 。
P6192 【模板】最小斯坦纳树
题意同上。
首先显然最终的生成子图一定是一棵树。我们对这棵隐树做类似树形 dp 的东西。先看看式子:
令 表示第 个节点为根的子树,包含 关键点集合的最小边权和。
有两个转移:
- 对于 ,。
- 对于 ,。
初始 ,如果 那么 。
其中 是 。
我们分类讨论证明这个转移是正确的:
首先如果 是 的父亲,那么会白白多算,是不可能真正转移到的。
然后如果 有若干个儿子,其可以从每一个儿子上转移而来,再通过类似树上背包的方式合并。
考虑转移顺序。显然有第二维度从低到高的拓扑结构。枚举第二维度,可以先做第二个转移,对于每一个 ,枚举子集耗费 。然后做第一个转移,这个转移式子是 ,发现很像 dijkstra 结构。于是可以用稍微变形的 dijkstra 做。
时间复杂度是 。
GYM 100722 D Ticket to Ride
【题意】
给定 点 边的图,给定 个节点对,要求选出一些边使得这 对节点两两联通。
【分析】
可以考虑先 枚举哪些对节点是连在一起的,然后求最小斯坦纳树,再合并。
也可以去掉 :发现 dp 的时候其实做了所有关键点集合的答案,再做一次 dp 合并即可。
map<string, int> mp; int n, m; int fa[33];
int get(int x) {if(fa[x] == x) return x; else return fa[x] = get(fa[x]);}
void merge(int x, int y) {x = get(x); y = get(y); fa[x] = y; }
vector<pii> g[33]; int key[4][2]; int dp[33][1<<8]; bool vis[33];
int ans[1<<4];
int getans(int state) {
vector<int> pt;
f(i, 0, 3) if((state >> i) & 1) {
pt.push_back(key[i][0]); pt.push_back(key[i][1]);
}
sort(pt.begin(), pt.end());
int cnt = unique(pt.begin(), pt.end()) - pt.begin() - 1;
f(i, 1, n) f(j, 0, (1 << (cnt + 1)) - 1) dp[i][j] = inf;
f(i, 0, cnt) dp[pt[i]][(1<<i)] = 0;
f(i, 1, n) dp[i][0] = 0;
f(i, 0, (1 << (cnt + 1)) - 1) {
f(j, 1, n) {
for(int t = i; t >= 0; t = (t - 1) & i) {
cmin(dp[j][i], dp[j][t] + dp[j][i - t]);
if(t == 0) break;
}
}
priority_queue<pii> q;
int mn = 0;
f(j, 1, n) vis[j] = 0;
f(j, 1, n)if(mn == 0 || dp[j][i] < dp[mn][i]) {mn = j;}
q.push({-dp[mn][i], mn});
while(!q.empty()) {
pii now = q.top(); q.pop();
if(vis[now.second]) continue;
vis[now.second] = 1; cmin(dp[now.second][i], -now.first);
for(pii j : g[now.second]) {
if(vis[j.first]) continue;
q.push({-dp[now.second][i] - j.second, j.first});
}
}
}
int ret = inf;
f(i, 1, n) cmin(ret, dp[i][(1 << (cnt + 1)) - 1]);
return ret;
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(NULL);
cout.tie(NULL);
//freopen();
//freopen();
//time_t start = clock();
//think twice,code once.
//think once,debug forever.
while(cin >> n >> m && n != 0) {
mp.clear(); f(i, 1, n) {string s; cin >> s; mp[s] = i; g[i].clear(); }
f(i, 1, m) {
string s, t; cin >> s >> t; int w; cin >> w;
g[mp[s]].push_back({mp[t], w});
g[mp[t]].push_back({mp[s], w});
}
f(i, 0, 3) f(j, 0, 1) {
string u; cin >> u; key[i][j] = mp[u];
}
f(i, 0, 15) ans[i] = getans(i);
f(i, 0, 15) for(int j = i; ; j = (j - 1) & i) {
cmin(ans[i], ans[j] + ans[i - j]); if(j == 0) break;
}
cout << ans[15] << endl;
}
//time_t finish = clock();
//cout << "time used:" << (finish-start) * 1.0 / CLOCKS_PER_SEC <<"s"<< endl;
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 如何调用 DeepSeek 的自然语言处理 API 接口并集成到在线客服系统
· 【译】Visual Studio 中新的强大生产力特性
· 2025年我用 Compose 写了一个 Todo App