前言

如果你对这篇文章可感兴趣,可以点击「【访客必读 - 指引页】一文囊括主页内所有高质量博客」,查看完整博客分类与对应链接。

斯坦纳树概述

一、适用问题

一个图中,有若干个关键点,将这几个关键点连在一起的最小花费。直观的理解,就是带关键节点的最小生成树。

当然不同的题会有不同的限制,比如关键节点必须是叶子节点,或者求一个斯坦纳森林(需要对斯坦纳树再进行一次状压)。

二、DP思想

在图上找一个树,应该如何定状态呢?

还记得第一次遇到斯坦纳树问题时,状态定的五花八门,眼花缭乱的,怼了一整场也只是空耗时间。

其实,斯坦纳树的状态应该定为 dp[i][state]dp[i][state]dp[i][state],表示以 iii 为根,关键点在当前斯坦纳树中的连通状态为 statestatestate。

状态转移分成两部分。

第一部分,枚举连通状态的子集。
dp[i][state]=min(dp[i][subset1]+dp[i][subset2])dp[i][state]=min(dp[i][subset_1]+dp[i][subset_2]) dp[i][state]=min(dp[i][subset1​]+dp[i][subset2​])
第二部分,枚举树上边进行松弛。
dp[i][state]=min(dp[i][state]+dp[j][state]+e[i][j])dp[i][state]=min(dp[i][state]+dp[j][state]+e[i][j]) dp[i][state]=min(dp[i][state]+dp[j][state]+e[i][j])
可以发现,第二部分的状态一致,因此可以变为最短路问题,用 spfaspfaspfa 或者 dijkstradijkstradijkstra 进行转移。

三、复杂度分析

O(n∗3k+c∗∣E∣∗2k)O(n*3^k+c*|E|*2^k)O(n∗3k+c∗∣E∣∗2k)
nnn 为点数,∣E∣|E|∣E∣ 为边数,kkk 为关键点数,ccc 为 spfaspfaspfa 常数,前一部分为子集枚举的复杂度,第二部分为枚举边松弛的复杂度。

四、模板

下图只是大致模板,应对具体题目时需要进行一些改动,可以通过下述习题更深刻地感受这一点。

#include <bits/stdc++.h>
#define rep(i,a,b) for(int i = a; i <= b; i++)
const int N = 30+5;
const int K = 10;
const int inf = 1e8;
using namespace std;int dp[N][1<<K], st[N], endS;
bool vis[N][1<<K];
queue<int> q;//初始化函数
void init(){//定义末状态endS = (1<<K)-1;//初始化rep(i,0,n)rep(S,0,endS) dp[i][S] = inf, vis[i][S] = 0;//求每个点的状态, 假设前k个点为特殊点rep(i,1,k) st[i] = 1<<(i-1);//dp函数关键点赋初值rep(i,1,k) dp[i][st[i]] = 0;
}//斯坦纳树第二部分转移
void SPFA(int state){while(q.size()){int x = q.front(); q.pop(); vis[x][state] = 0;//枚举连边for(int i = head[x]; i; i = e[i].next){int y = e[i].to;//松弛操作if(dp[y][st[y]|state] > dp[x][state]+e[i].w){dp[y][st[y]|state] = dp[x][state]+e[i].w;//状态保持一致或已经在队列中了if((st[y]|state) != state || vis[y][state]) continue;vis[y][state] = 1;q.push(y);}}}
}//斯坦纳树主函数
void steinerTree(){rep(S,1,endS){rep(i,1,n){//i为关键节点,判断i是否在状态S中if(st[i] && (S|st[i]) != S) continue;//第一部分转移,枚举子集for(int sub = S&(S-1); sub; sub = (sub-1)&S){ int x = st[i]|sub, y = st[i]|(S-sub);if(dp[i][x] != inf && dp[i][y] != inf)dp[i][S] = min(dp[i][S],dp[i][x]+dp[i][y]);}if(dp[i][S] != inf)q.push(i), vis[i][S] = 1;}//第二部分转移,对于每个状态进行一次转移SPFA(S);}
}

斯坦纳树习题

1. Joining Capitals

题意: NNN 个点,KKK 个关键点,每个点有一个二维坐标,两点之间的距离为欧式距离。现在需要将 KKK 个关键点用最小花费连接起来,并且关键点的度数为 111,求最小花费。(4≤N≤100,3≤K≤min(10,N))(4\leq N\leq 100, 3\leq K\leq min(10,N))(4≤N≤100,3≤K≤min(10,N))

思路: 这是一道模板题,唯一的变化仅在于关键点度数不能超过 111,并且 KKK 大于 333,因此可以发现对于最后的结果来说,树根一定不为关键节点。

因此在转移的时候不要令树根为关键节点即可,其余与普通斯坦纳树一致。

代码:

#include <bits/stdc++.h>
#define __ ios::sync_with_stdio(0);cin.tie(0);cout.tie(0)
#define rep(i,a,b) for(int i = a; i <= b; i++)
#define LOG1(x1,x2) cout << x1 << ": " << x2 << endl;
#define LOG2(x1,x2,y1,y2) cout << x1 << ": " << x2 << " , " << y1 << ": " << y2 << endl;
#define LOG3(x1,x2,y1,y2,z1,z2) cout << x1 << ": " << x2 << " , " << y1 << ": " << y2 << " , " << z1 << ": " << z2 << endl;
typedef long long ll;
typedef double db;
const int N = 100+100;
const db EPS = 1e-9;
const db inf = 1e8;
using namespace std;int n,k,endS,st[N];
struct Point{int x,y;
}P[N]; //前k个为关键点
db dptree[N][1<<11];
bool vis[N][1<<11];
queue<int> que;void initSteinerTree()
{endS = (1<<k)-1;rep(i,0,n)rep(S,0,endS) dptree[i][S] = inf;rep(i,1,k)st[i] = 1<<(i-1); //注意状态从偏移0开始rep(i,1,n) memset(vis[i],0,sizeof vis[i]);rep(i,1,n) dptree[i][st[i]] = 0;
}void SPFA(int state){while(que.size()){int x = que.front(); que.pop();vis[x][state] = false;rep(i,k+1,n){if(i == x) continue;db w = sqrt((db)(P[x].x-P[i].x)*(P[x].x-P[i].x)+(db)(P[x].y-P[i].y)*(P[x].y-P[i].y));if(dptree[i][st[i]|state]-(dptree[x][state]+w) > 1e-6){dptree[i][st[i]|state] = dptree[x][state]+w;if((st[i]|state) != state || vis[i][state]) continue;vis[i][state] = true;que.push(i);}}}
}void steinerTree()
{rep(S,0,endS){//枚举树的子集rep(i,1,n){//子集枚举if(i > k){for(int sub = (S-1)&S; sub; sub=(sub-1)&S){int x = sub, y = S-sub;if(dptree[i][x] != inf && dptree[i][y] != inf)dptree[i][S] = min(dptree[i][S],dptree[i][x]+dptree[i][y]);}}if(dptree[i][S] != inf) que.push(i),vis[i][S]=true;}//枚举树的边SPFA(S);}
}int main()
{scanf("%d%d",&n,&k);rep(i,1,n) scanf("%d%d",&P[i].x,&P[i].y);initSteinerTree();steinerTree();db ans = inf;rep(i,k+1,n)ans = min(ans,dptree[i][endS]);printf("%.5f\n",ans);return 0;
}
/*
题意:一共n个点,前k个为关键节点,问将k个关键节点连通且每个关键节点度数为1的最小花费思路:关键节点不能作为树根,其余与普通斯坦纳树一致
*/
2. Attack

题意: nnn 个点,mmm 条边,每次给出四对点(每对点可能会有重复),问在图中选择一些边,让每对点连通的最小代价,注意并不要求所有点互相连通,仅要求每对点连通。(1≤n≤30,0≤m≤1000)(1\leq n\leq 30,0\leq m\leq 1000)(1≤n≤30,0≤m≤1000)

思路: 这其实是一道斯坦纳树森林问题,因为最后的结果并不要求所有点连通。关于斯坦纳树森林我们到下一题再进行讲解。由于此处只有四对点,因此我们可以通过枚举完成。(虽然重复代码很多)

首先用模板求出斯坦纳树的各个状态值。然后枚举最后森林的结构。分别是所有点连通(111 棵树),每对点连通(444 棵树),一对点连通,其余三对连通(1+31+31+3),两对点连通(2+22+22+2)。当时还不会斯坦纳森林,因此有了这种 (巧妙) 憨憨的做法,推荐直接写下一题的森林,然后再上来秒杀这题,顺便嘲笑博主…

代码:

#include <bits/stdc++.h>
#define __ ios::sync_with_stdio(0);cin.tie(0);cout.tie(0)
#define rep(i,a,b) for(int i = a; i <= b; i++)
#define LOG1(x1,x2) cout << x1 << ": " << x2 << endl;
#define LOG2(x1,x2,y1,y2) cout << x1 << ": " << x2 << " , " << y1 << ": " << y2 << endl;
#define LOG3(x1,x2,y1,y2,z1,z2) cout << x1 << ": " << x2 << " , " << y1 << ": " << y2 << " , " << z1 << ": " << z2 << endl;
typedef long long ll;
typedef double db;
const db EPS = 1e-9;
const int N = 30+10;
const int M = 2*1000+10;
const ll inf = 1e13;
using namespace std;int n,m,head[N],tot,st[N],v[6],endS,cnt,dp[N][1<<9];
//cnt为几个关键点
bool vis[N][1<<9];
map<string,int> mp;
struct Node{int to,next,w;
}e[M];
queue<int> q;void add(int x,int y,ll w){e[++tot].to = y, e[tot].next = head[x], head[x] = tot, e[tot].w = w;
}void initSteinerTree(){endS = (1<<cnt)-1;   rep(i,0,n)rep(S,0,endS) dp[i][S] = inf, vis[i][S] = 0;rep(i,1,n)if(st[i]) dp[i][st[i]] = 0;
}void SPFA(int state){while(q.size()){int x = q.front(); q.pop(); vis[x][state] = 0;for(int i = head[x]; i; i = e[i].next){int y = e[i].to;if(y == x) continue;if(dp[y][st[y]|state] > dp[x][state]+e[i].w){dp[y][st[y]|state] = dp[x][state]+e[i].w;if((st[y]|state) != state || vis[y][state|st[y]]) continue;vis[y][state|st[y]] = 1;q.push(y);}}}
}void SteinerTree(){rep(S,1,endS){//枚举子集rep(i,1,n){if(st[i] && (st[i]&S) == 0) continue;for(int sub = (S-1)&S; sub; sub = (sub-1)&S){int x = st[i]|sub, y = st[i]|(S-sub);if(dp[i][x] != inf && dp[i][y] != inf)dp[i][S] = min(dp[i][S],dp[i][x]+dp[i][y]);}if(dp[i][S] != inf)q.push(i), vis[i][S] = 1;}//枚举边SPFA(S);}
}int main()
{__; tot = 1;cin >> n >> m;rep(i,1,n){string tp; cin >> tp;mp[tp] = i;}rep(i,1,m){string a,b; cin >> a >> b;int w; cin >> w;// LOG1("w",w);int x1 = mp[a], x2 = mp[b];add(x1,x2,w); add(x2,x1,w);}cnt = 0;rep(i,1,4){string a,b; cin >> a >> b;int x1 = mp[a], x2 = mp[b];if(!st[x1]) {st[x1] = 1<<cnt; cnt++;}if(!st[x2]) {st[x2] = 1<<cnt; cnt++;}v[i] = st[x1]|st[x2];// LOG2("i",i,"v[i]",v[i]);}initSteinerTree();SteinerTree();ll ans = inf;//4对一起// LOG1("cnt",cnt);rep(i,1,n){int S = 0;rep(j,1,4) S |= v[j];ans = min(ans,dp[i][S]);}//3+1// LOG1("ans1",ans);rep(i,1,4){int S1 = v[i],S2 = 0;rep(j,1,4)if(j != i) S2 |= v[j];rep(k1,1,n){if((st[k1]|S1) != S1) continue;rep(k2,1,n)if((st[k2]|S2) != S2) continue;else ans = min(ans,dp[k1][S1]+dp[k2][S2]); }}// LOG1("ans2",ans);//2+2rep(i1,1,4)rep(i2,i1+1,4){int S1 = v[i1]|v[i2], S2 = 0;rep(j,1,4)if(j != i1 && j != i2) S2 |= v[j];// LOG2("i1",i1,"i2",i2);rep(k1,1,n){if((st[k1]|S1) != S1) continue;rep(k2,1,n)if((st[k2]|S2) != S2) continue;else ans = min(ans,dp[k1][S1]+dp[k2][S2]); }       }//2+1+1rep(i1,1,4)rep(i2,i1+1,4){int S1 = v[i1]|v[i2], S2 = 0, S3 = 0, id1 = 0;rep(j,1,4)if(j != i1 && j != i2) {S2 |= v[j]; id1 = j; break;}rep(j,1,4)if(j != i1 && j != i2 && j != id1) {S3 |= v[j]; break;}// LOG2("i1",i1,"i2",i2);rep(k1,1,n){if((st[k1]|S1) != S1) continue;rep(k2,1,n){if((st[k2]|S2) != S2) continue;rep(k3,1,n)if((st[k3]|S3) != S3) continue;else ans = min(ans,dp[k1][S1]+dp[k2][S2]+dp[k3][S3]);}}        }//1+1+1+1rep(k1,1,n){if((st[k1]|v[1]) != v[1]) continue;rep(k2,1,n){if((st[k2]|v[2]) != v[2]) continue;rep(k3,1,n){if((st[k3]|v[3]) != v[3]) continue;rep(k4,1,n)if((st[k4]|v[4]) != v[4]) continue;else ans = min(ans,dp[k1][v[1]]+dp[k2][v[2]]+dp[k3][v[3]]+dp[k4][v[4]]);}}}// LOG1("n",n);printf("%d\n",ans);return 0;
}
3. Peach Blossom Spring

题意: nnn 个点,mmm 条边,前 kkk 个点为居住处,后 kkk 个点为避难处。现在 kkk 个居民需要从居住处逃避到避难处,问最少的连边的花费。注意只要满足居民有避难处可待即可,因此是个森林。(1≤n≤50,0≤m≤1000,1≤k≤5,2∗k≤n)(1\leq n\leq 50, 0\leq m\leq 1000,1\leq k\leq 5,2*k\leq n)(1≤n≤50,0≤m≤1000,1≤k≤5,2∗k≤n)

思路: 这其实是个斯坦纳森林模板题。首先正常的求出斯坦纳树的结果,即 dp[i][state]dp[i][state]dp[i][state] 表示第 iii 个点为树根,关键点连通状态为 statestatestate 的最小花费。

然后再设置一个新的函数 f[1<<K]f[1<<K]f[1<<K],f[state]f[state]f[state] 表示关键节点连通状态为 statestatestate 时的最小代价。由于答案可以是森林,因此枚举 statestatestate 的子状态进行转移即可。
dp[i][state]=min(dp[i][state],dp[i][sub]+dp[i][state−sub])dp[i][state] = min(dp[i][state],dp[i][sub]+dp[i][state-sub]) dp[i][state]=min(dp[i][state],dp[i][sub]+dp[i][state−sub])
这里需要注意要检验状态 statestatestate 是否合法,statestatestate 状态合法当且仅当该状态中居住处点个数与避难处点个数相同。

代码:

#include <bits/stdc++.h>
#define __ ios::sync_with_stdio(0);cin.tie(0);cout.tie(0)
#define rep(i,a,b) for(int i = a; i <= b; i++)
#define LOG1(x1,x2) cout << x1 << ": " << x2 << endl;
#define LOG2(x1,x2,y1,y2) cout << x1 << ": " << x2 << " , " << y1 << ": " << y2 << endl;
#define LOG3(x1,x2,y1,y2,z1,z2) cout << x1 << ": " << x2 << " , " << y1 << ": " << y2 << " , " << z1 << ": " << z2 << endl;
typedef long long ll;
typedef double db;
const db EPS = 1e-9;
const int N = 50+5;
const int M = 2*1000+100;
const int inf = 1e8;
using namespace std;int n,m,k,head[N],dp[N][1<<10],f[1<<10],endS,st[N],tot;
bool vis[N][1<<10];
struct Node{int to,next,w;
}e[M];
queue<int> q;void add(int x,int y,int w){e[++tot].to = y, e[tot].next = head[x], head[x] = tot, e[tot].w = w;
}void init(){tot = 1;rep(i,0,n) head[i] = 0;endS = (1<<(2*k))-1;rep(i,0,n) memset(vis[i],0,sizeof vis[i]);rep(i,0,n) st[i] = 0;rep(i,0,n)rep(S,0,endS) dp[i][S] = inf, vis[i][S] = 0;rep(i,1,k) st[i] = 1<<(i-1);rep(i,n-k+1,n) st[i] = 1<<(i-(n-k)+k-1);rep(i,1,n)if(st[i]) dp[i][st[i]] = 0;rep(S,0,endS) f[S] = inf;
}void SPFA(int state){while(q.size()){int x = q.front(); q.pop(); vis[x][state] = 0;for(int i = head[x]; i; i = e[i].next){int y = e[i].to;// LOG2("x",x,"y",y);if(y == x) continue;if(dp[y][st[y]|state] > dp[x][state]+e[i].w){dp[y][st[y]|state] = dp[x][state]+e[i].w;if((st[y]|state) != state || vis[y][state]) continue;q.push(y); vis[y][state] = 1;}}}
}void steinerTree(){rep(S,1,endS){rep(i,1,n){//i在S中if(st[i] && (st[i]|S) != S) continue;//枚举子集for(int sub = (S-1)&S; sub; sub = (sub-1)&S){int x = st[i]|sub, y = st[i]|(S-sub);// LOG2("x",x,"y",y);if(dp[i][x] != inf && dp[i][y] != inf)dp[i][S] = min(dp[i][S],dp[i][x]+dp[i][y]);}if(dp[i][S] != inf){q.push(i), vis[i][S] = 1;// LOG3("i",i,"S",S,"dp[i][S]",dp[i][S])}}SPFA(S);}
}int check(int state){int cnt = 0;rep(i,0,2*k-1){if(state&(1<<i))cnt += i<k?1:-1;}// LOG2("state",state,"cnt",cnt);return cnt == 0 ? 1:0;
}void solve(){rep(S,1,endS)if(check(S)){// LOG1("S",S);rep(i,1,n)f[S] = min(f[S],dp[i][S]);}rep(S,1,endS)for(int sub = S&(S-1); sub; sub = (sub-1)&S){int x = sub, y = S-sub;if(check(x) && check(y))f[S] = min(f[S],f[x]+f[y]);}if(f[endS] == inf) printf("No solution\n");else printf("%d\n",f[endS]);
}int main()
{int _; scanf("%d",&_);while(_--){scanf("%d%d%d",&n,&m,&k);init();rep(i,1,m){int x,y,w; scanf("%d%d%d",&x,&y,&w);add(x,y,w); add(y,x,w);}steinerTree();solve();}return 0;
}
4. [Wc2008] 游览计划

题意: n∗mn*mn∗m 的格子,每个格子上的值表示这个点上的志愿者个数,若该点为 000,表示该点为景区。问最少需要多少个志愿者,可以连通所有的景区,需要输出路径。(1≤n,m,k≤10)(1\leq n,m,k\leq 10)(1≤n,m,k≤10)

思路: 答案好求,直接上板子(板子还是理解完记住比较好,养成学一个记住一个的好习惯)。路径输出的话,我们考虑一下 dpdpdp 的两部分转移方程。

可以发现 (i,j,state)(i,j,state)(i,j,state) 构成了一个状态,(i,j)(i,j)(i,j) 表示坐标。每个状态可能由一个或两个子状态转移而来,因此我们用 pre[i][j][state]pre[i][j][state]pre[i][j][state] 结构体记住两个子状态,输出路径的时候 dfsdfsdfs 递归所有子状态进行格点标记即可。

所有的习题就到这了,祝大家 AAA 题顺利。

代码:

#include <bits/stdc++.h>
#define __ ios::sync_with_stdio(0);cin.tie(0);cout.tie(0)
#define rep(i,a,b) for(int i = a; i <= b; i++)
#define LOG1(x1,x2) cout << x1 << ": " << x2 << endl;
#define LOG2(x1,x2,y1,y2) cout << x1 << ": " << x2 << " , " << y1 << ": " << y2 << endl;
#define LOG3(x1,x2,y1,y2,z1,z2) cout << x1 << ": " << x2 << " , " << y1 << ": " << y2 << " , " << z1 << ": " << z2 << endl;
typedef long long ll;
typedef double db;
const db EPS = 1e-9;
const int N = 11;
const int inf = 1e8;
using namespace std;int n,m,a[N][N],st[N][N],cnt,dp[N][N][1<<N],endS,mp[N][N];
bool vis[N][N][1<<N];
int dir[4][2] = {{-1,0},{1,0},{0,1},{0,-1}};
queue<pair<int,int> > q;
struct Node{int x,y,s1,s2;Node() {x = y = s1 = s2 = 0;}Node(int a,int b,int c,int d):x(a),y(b),s1(c),s2(d) {}
}pre[N][N][1<<N];void init(){rep(i,1,n)rep(j,1,m){scanf("%d",&a[i][j]);if(a[i][j] == 0) st[i][j] = 1<<cnt, cnt++;}endS = (1<<cnt)-1;rep(i,0,n)rep(j,0,m)rep(S,0,endS) dp[i][j][S] = inf, vis[i][j][S] = 0;//DP赋初值rep(i,0,n)rep(j,0,m)if(st[i][j]) dp[i][j][st[i][j]] = 0;
}void SPFA(int state){while(q.size()){int x = q.front().first, y = q.front().second; q.pop(); vis[x][y][state] = 0;rep(i,0,3){int tx = x+dir[i][0], ty = y+dir[i][1];if(tx < 1 || tx > n || ty < 1 || ty > m) continue;if(dp[tx][ty][state|st[tx][ty]] > dp[x][y][state]+a[tx][ty]){dp[tx][ty][state|st[tx][ty]] = dp[x][y][state]+a[tx][ty];//更新前驱pre[tx][ty][state|st[tx][ty]] = {x,y,state,state};if((state|st[tx][ty]) != state || vis[tx][ty][state]) continue;q.push(make_pair(tx,ty)); vis[tx][ty][state] = 1;}}}
}void steinertree(){rep(S,1,endS){rep(i,1,n)rep(j,1,m){if(st[i][j] && (st[i][j]|S) != S) continue;for(int sub = S&(S-1); sub; sub = (sub-1)&S){int x = sub|st[i][j], y = st[i][j]|(S-sub);if(dp[i][j][x] != inf && dp[i][j][y] != inf){if(dp[i][j][S] > dp[i][j][x]+dp[i][j][y]-a[i][j]){dp[i][j][S] = dp[i][j][x]+dp[i][j][y]-a[i][j];pre[i][j][S] = {i,j,x,y};}}}if(dp[i][j][S] != inf)q.push(make_pair(i,j)), vis[i][j][S] = 1;}SPFA(S);}
}void dfs(int x,int y,int state){if(vis[x][y][state] || x < 1 || x > n || y < 1 || y > m || !state) return;mp[x][y] = 1; vis[x][y][state] = 1;int tx = pre[x][y][state].x, ty = pre[x][y][state].y, s1 = pre[x][y][state].s1, s2 = pre[x][y][state].s2;if(s1 != s2){dfs(tx,ty,s1); dfs(tx,ty,s2);}else{dfs(tx,ty,s1);}
}void solve(){int ans = inf, xp = 0, yp = 0;rep(i,1,n)rep(j,1,m)if(dp[i][j][endS] < ans){ans = dp[i][j][endS]; xp = i, yp = j;}memset(vis,0,sizeof vis);dfs(xp,yp,endS);printf("%d\n",ans);rep(i,1,n){rep(j,1,m){if(a[i][j] == 0) printf("x");else if(mp[i][j] == 1) printf("o");else printf("_");}    printf("\n");}
}int main()
{scanf("%d%d",&n,&m);init();steinertree();solve();return 0;
}

斯坦纳树算法概述及习题相关推荐

  1. 斯坦纳问题的matlab代码,几类特殊斯坦纳最小树问题的研究

    撰写目的和基本思路 斯坦纳最小树是组合优化的重要问题,具有广泛的应用前景.通过本作品研究,探讨斯坦纳最小树的基本性质和判定方法,给出几个点数较少的斯坦纳最小树,设计其算法并交由计算机实现:在此基础上, ...

  2. bzoj1402 Ticket to Ride 斯坦纳树 + 状压dp

    给定\(n\)个点,\(m\)条边的带权无向图 选出一些边,使得\(4\)对点之间可达,询问权值最小为多少 \(n \leqslant 30, m \leqslant 1000\) 首先看数据范围,\ ...

  3. BZOJ 4006 Luogu P3264 [JLOI2015]管道连接 (斯坦纳树、状压DP)

    题目链接: (bzoj)https://www.lydsy.com/JudgeOnline/problem.php?id=4006 (luogu)https://www.luogu.org/probl ...

  4. [WC2008]游览计划(斯坦纳树)

    [Luogu4294] 题解 : 斯坦纳树 \(dp[i][j]\) 表示以\(i\)号节点为根,当前状态为\(j\)(与\(i\)连通的点为\(1\)) 当根\(i\)不改变时状态转移方程是: \( ...

  5. 业界萌新对斯坦纳树的小结

    业界萌新对斯坦纳树的小结 0.简介 斯坦纳树问题是组合优化问题,与最小生成树相似,是最短网络的一种.最小生成树是在给定的点集和边中寻求最短网络使所有点连通.而最小斯坦纳树允许在给定点外增加额外的点,使 ...

  6. [bzoj4006][JLOI2015]管道连接_斯坦纳树_状压dp

    管道连接 bzoj-4006 JLOI-2015 题目大意:给定一张$n$个节点$m$条边的带边权无向图.并且给定$p$个重要节点,每个重要节点都有一个颜色.求一个边权和最小的边集使得颜色相同的重要节 ...

  7. 【BZOJ4774】修路 [斯坦纳树]

    修路 Time Limit: 20 Sec  Memory Limit: 256 MB Description Input Output 仅一行一个整数表示答案. Sample Input 5 5 2 ...

  8. [APIO2013]机器人[搜索、斯坦纳树]

    题意 题目链接 分析 记 g(d,x,y) 表示从 (x,y) 出发,方向为 d 到达的点,这个可以通过记忆化搜索求出,注意如果转移成环(此时向这个方向走没有意义)要特判. 记 f(l,r,x,y) ...

  9. bzoj 4006 管道连接 —— 斯坦纳树+状压DP

    题目:https://www.lydsy.com/JudgeOnline/problem.php?id=4006 用斯坦纳树求出所有关键点的各种连通情况的代价,把这个作为状压(压的是集合选择情况)的初 ...

  10. [APIO2013]机器人(斯坦纳树)

    题目描述 VRI(Voltron 机器人学会)的工程师建造了 n 个机器人.任意两个兼容的机 器人站在同一个格子时可以合并为一个复合机器人. 我们把机器人用 1 至 n 编号(n ≤ 9).如果两个机 ...

最新文章

  1. htc820+android+l,首款高通64位八核 HTC Desire 820评测
  2. Python-EEG工具库MNE中文教程(13)-“bad“通道介绍
  3. 数据库面试题【十二、存储引擎选择】
  4. Android中列表动态删除item,如何删除Android ExpandableListView中某个group item的child item?...
  5. 今年第一个项目来说NET的中间语言
  6. 安卓调用系统语音识别功能全解(谷歌语音服务):获取识别结果,使用语音识别进行搜索。
  7. 资源过于硬核,8h删!这波福利....请笑纳~
  8. Win10使用Dism命令提取(备份)和还原驱动程序
  9. android模拟器root权限获取,如何在Android模拟器上获得root访问权限?
  10. 分享咖啡基础知识——从咖啡小白到咖啡发烧友需要了解的那些事儿!
  11. 时光轴一之listView实现时光轴效果
  12. 运筹学-2-单纯形法的矩阵计算
  13. 心态-《积极的力量》书中的精髓:如何保持积极乐观的心态,从而提升我们的幸福感?
  14. html中的keygen元素
  15. 利用U盘里的GHOST文件恢复系统
  16. 计算机系统(1) 实验五 中断实验
  17. 【算法知识】先验分布、后验分布、似然估计
  18. 牛客面试题HTML与CSS部分
  19. OpenDNS(转)
  20. 外行学计算机,《新手无忧学电脑:外行入门学电脑(2008至尊经典版)》低价购书_计算机与互联网_孔网...

热门文章

  1. Codeforces Round #215 (Div. 2) 解题报告
  2. java代理模式与反射机制
  3. SSM俱乐部商城 俱乐部官网商城
  4. STC学习:乒乓球游戏
  5. 小h的数列 //差分前缀和的应用(好好看好好学(包括我自己))
  6. 数据可视化——ECharts基础
  7. python安装request方法mac_Mac下python3使用requests库出现No module named 'requests'解决方法...
  8. Matplotlib_库的安装
  9. java人员工作建议_给JAVA设计开发新手的一些建议和意见(1)
  10. 基于python的毕业论文邮箱收发系统_基于Python实现邮件发送