$dp[i][state]$ 表示以$i$为根,指定集合中的点的连通状态为state的生成树的最小总权值
有两种转移方向:
1、先通过连通状态的子集进行转移。
2、在当前枚举的连通状态下,对该连通状态进行松弛操作。
P4294 [WC2008]游览计划
注意景点的个数不超过10个。
$dp[i][j][state]$ 表示在$[i, j]$这个点与state中对应点连通的最小代价。
那么就可以用状压DP + spfa求解。
由于要输出方案,可以记录每个状态的前一个状态,最后dfs跑一遍就行了。
// #pragma GCC optimize(2) // #pragma GCC optimize(3) // #pragma GCC optimize(4) #include <algorithm> #include <iterator> #include <iostream> #include <cstring> #include <cstdlib> #include <iomanip> #include <bitset> #include <cctype> #include <cstdio> #include <string> #include <vector> #include <stack> #include <cmath> #include <queue> #include <list> #include <map> #include <set> #include <cassert> #include <unordered_set> #include <unordered_map> // #include<bits/extc++.h> // using namespace __gnu_pbds; using namespace std; #define pb push_back #define fi first #define se second #define debug(x) cerr<<#x << " := " << x << endl; #define bug cerr<<"-----------------------"<<endl; #define FOR(a, b, c) for(int a = b; a <= c; ++ a) typedef long long ll; typedef long double ld; typedef pair<int, int> pii; typedef pair<ll, ll> pll; const int inf = 0x3f3f3f3f; const ll inff = 0x3f3f3f3f3f3f3f3f; const int mod = 1e9+7; template<typename T> inline T read(T&x){ x=0;int f=0;char ch=getchar(); while (ch<'0'||ch>'9') f|=(ch=='-'),ch=getchar(); while (ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar(); return x=f?-x:x; } /**********showtime************/ const int maxn = 12; int a[maxn][maxn]; int dp[maxn][maxn][1055]; struct node{ int x, y, state; } pre[maxn][maxn][1055]; queue<pii>que; int nx[4][2] = { {0, 1}, {1, 0},{-1,0},{0,-1} }; int n,m; int vis[maxn][maxn]; void spfa(int now) { while(!que.empty()) { pii tmp = que.front(); que.pop(); for(int i=0; i<4; i++) { int x = tmp.fi + nx[i][0]; int y = tmp.se + nx[i][1]; if(x < 1 || x > n || y < 1 || y > m) continue; if(dp[x][y][now] > dp[tmp.fi][tmp.se][now] + a[x][y]) { dp[x][y][now] = dp[tmp.fi][tmp.se][now] + a[x][y]; pre[x][y][now] = node{tmp.fi, tmp.se, now}; if(!vis[x][y]) { que.push(pii(x, y)); vis[x][y] = 1; } } } vis[tmp.fi][tmp.se] = 0; } } void dfs(int x, int y, int now) { if(x == 0 || y == 0) return; vis[x][y] = 1; node tmp = pre[x][y][now]; dfs(tmp.x, tmp.y, tmp.state); if(tmp.x == x && tmp.y == y) dfs(tmp.x, tmp.y, now - tmp.state); } int main(){ scanf("%d%d", &n, &m); memset(dp, inf, sizeof(dp)); int num = 0; for(int i=1; i<=n; i++) { for(int j=1; j<=m; j++) { scanf("%d", &a[i][j]); if(a[i][j] == 0) { dp[i][j][(1<<num)] = 0; num++; } } } int all = (1<<num) - 1; for(int state = 0; state <= all; state ++) { for(int i=1; i<=n; i++) { for(int j=1; j<=m; j++) { for(int s0 = (s0-1) & state; s0; s0 = (s0-1) & state) { if(dp[i][j][state] > dp[i][j][s0] + dp[i][j][state - s0] - a[i][j]) { dp[i][j][state] = dp[i][j][s0] + dp[i][j][state - s0] - a[i][j]; pre[i][j][state] = node{i, j, s0}; } } if(dp[i][j][state] < inf) que.push(pii(i, j)), vis[i][j] = 1; } } spfa(state); } int ax, ay, mn = inf; for(int i=1; i<=n; i++) { for(int j=1; j<=m; j++) { if(dp[i][j][all] < mn) { mn = dp[i][j][all]; ax = i; ay = j; } } } printf("%d\n", mn); memset(vis, 0, sizeof(vis)); dfs(ax, ay, all); for(int i=1; i<=n; i++) { for(int j=1; j<=m; j++) { if(a[i][j] == 0) printf("x"); else if(vis[i][j]) printf("o"); else printf("_"); } puts(""); } return 0; }
HDU-4085 Peach Blossom Spring
给定一个$n \le 50 , m \le 1000$ 的无向图,让你用最小的修路总花费,使得1到k号点($k \le 5$),与最后k个点相连,就是说1到k号点每个点都有一个匹配点,匹配点两辆不同。
斯坦纳树,但是题目没有要求这$2 \times k$个点都连通,所以我们利用斯坦纳小树dp转移,求出斯坦纳森林。
// #pragma GCC optimize(2) // #pragma GCC optimize(3) // #pragma GCC optimize(4) #include <algorithm> #include <iterator> #include <iostream> #include <cstring> #include <cstdlib> #include <iomanip> #include <bitset> #include <cctype> #include <cstdio> #include <string> #include <vector> #include <stack> #include <cmath> #include <queue> #include <list> #include <map> #include <set> #include <cassert> #include <unordered_set> #include <unordered_map> // #include<bits/extc++.h> // using namespace __gnu_pbds; using namespace std; #define pb push_back #define fi first #define se second #define debug(x) cerr<<#x << " := " << x << endl; #define bug cerr<<"-----------------------"<<endl; #define FOR(a, b, c) for(int a = b; a <= c; ++ a) typedef long long ll; typedef long double ld; typedef pair<int, int> pii; typedef pair<ll, ll> pll; const int inf = 0x3f3f3f3f; const ll inff = 0x3f3f3f3f3f3f3f3f; const int mod = 1e9+7; template<typename T> inline T read(T&x){ x=0;int f=0;char ch=getchar(); while (ch<'0'||ch>'9') f|=(ch=='-'),ch=getchar(); while (ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar(); return x=f?-x:x; } /**********showtime************/ const int maxn = 55; vector<pii>mp[maxn]; queue<int>que; int vis[55]; int dp[maxn][1055],g[1055]; void spfa(int now) { while(!que.empty()) { int u = que.front(); que.pop(); for(pii p : mp[u]) { if(dp[p.fi][now] > dp[u][now] + p.se) { dp[p.fi][now] = dp[u][now] + p.se; if(vis[p.fi] == 0) { vis[p.fi] = 1; que.push(p.fi); } } } vis[u] = 0; } } int n,m,k; bool check(int state ){ int cnt = 0; for(int i=1; i<=k; i++) { if(state % 2 == 1) cnt++; state = state / 2; } for(int i=1; i<=k; i++) { if(state % 2 == 1) cnt--; state = state / 2; } return cnt == 0; } int main(){ int T; scanf("%d", &T); while(T--) { scanf("%d%d%d", &n, &m, &k); for(int i=1; i<=m; i++) { int u,v,w; scanf("%d%d%d", &u, &v, &w); mp[u].pb(pii(v, w)); mp[v].pb(pii(u, w)); } int num = 2 * k; int all = (1 << num) - 1; for(int i=1; i<=n; i++) for(int state = 0; state <= all; state ++ ) dp[i][state] = inf; for(int i=1; i<=k; i++) dp[i][(1<<(i-1))] = 0; for(int i=n, cur = 2*k; i>=n-k+1; i--, cur--) { dp[i][1<<(cur-1)] = 0; } for(int state=0; state <= all; state ++) { for(int i=1; i<=n; i++) { for(int s0 = (state-1)&state; s0; s0 = (s0-1) & state) { dp[i][state] = min(dp[i][state], dp[i][s0] + dp[i][state - s0]); } if(dp[i][state] < inf) que.push(i), vis[i] = 1; } spfa(state); } //由于最后没有要求得出一个斯坦纳树,而是一个斯坦纳森林,于是 //用小斯坦纳树组合一下 for(int state=0; state<=all; state++) { g[state] = inf; if(check(state)) { for(int i=1; i<=n; i++) g[state] = min(g[state], dp[i][state]); } } for(int state = 0; state <= all; state++) { if(check(state) == 0) continue; for(int s0 = (state-1)&state; s0; s0 = (s0-1) & state) { if(check(s0)) { g[state] = min(g[state], g[s0] + g[state - s0]); } } } if(g[all] < inf) printf("%d\n", g[all]); else printf("No solution\n"); for(int i=1; i<=n; i++) mp[i].clear(); } return 0; }
ZOJ-3613 Wormhole Transport
题意:
有n个星球,其中最多四个星球是资源星球,最多四个星球有不同个数的工厂。
一个资源星球只能供给一个工厂。
有不同类型的路可以修,问在最多工厂被供给的前提下,最小的修路费用。
思路:
朴素的斯坦纳树转移。
然后需要通过森林DP,转移条件是工厂个数 $\ge$ 资源个数
// #pragma GCC optimize(2) // #pragma GCC optimize(3) // #pragma GCC optimize(4) #include <algorithm> #include <iterator> #include <iostream> #include <cstring> #include <cstdlib> #include <iomanip> #include <bitset> #include <cctype> #include <cstdio> #include <string> #include <vector> #include <stack> #include <cmath> #include <queue> #include <list> #include <map> #include <set> #include <cassert> #include <unordered_set> #include <unordered_map> // #include<bits/extc++.h> // using namespace __gnu_pbds; using namespace std; #define pb push_back #define fi first #define se second #define debug(x) cerr<<#x << " := " << x << endl; #define bug cerr<<"-----------------------"<<endl; #define FOR(a, b, c) for(int a = b; a <= c; ++ a) typedef long long ll; typedef long double ld; typedef pair<int, int> pii; typedef pair<ll, ll> pll; const int inf = 0x3f3f3f3f; const ll inff = 0x3f3f3f3f3f3f3f3f; const int mod = 1e9+7; template<typename T> inline T read(T&x){ x=0;int f=0;char ch=getchar(); while (ch<'0'||ch>'9') f|=(ch=='-'),ch=getchar(); while (ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar(); return x=f?-x:x; } /**********showtime************/ const int maxn = 205; int pt[10], flag[10]; int vis[maxn]; queue<int>que; pii p[maxn]; vector<pii>mp[maxn]; int dp[maxn][300]; int g[300]; void spfa(int now) { while(!que.empty()) { int u = que.front(); que.pop(); for(pii p : mp[u]) { if(dp[p.fi][now] > dp[u][now] + p.se) { dp[p.fi][now] = dp[u][now] + p.se; if(vis[p.fi] == 0) { que.push(p.fi); vis[p.fi] = 1; } } } vis[u] = 0; } } int cnt = 0; bool check(int now) { int c[2]; c[0] = c[1] = 0; for(int i=0; i<cnt; i++) { if(now % 2 == 1) { if(flag[i]) c[1]++; else c[0] += pt[i]; } now = now / 2; } return c[1] <= c[0]; } int cal(int now) { int res = 0; for(int i=0; i<cnt; i++) { if(now % 2 == 1) { res += flag[i]; } now = now / 2; } return res; } int main(){ int n; while(~scanf("%d", &n)) { for(int i=1; i<=n; i++) { for(int j=0; j<300; j++) dp[i][j] = inf; } cnt = 0; int res1 = 0; for(int i=1; i<=n; i++) { scanf("%d%d", &p[i].fi, &p[i].se); if(p[i].fi && p[i].se) { res1++; p[i].se = 0; p[i].fi--; } if(p[i].se) { flag[cnt] = 1; // pt[cnt] = p[i].fi; dp[i][1<<cnt] = 0; cnt++; } else if(p[i].fi) { pt[cnt] = p[i].fi; flag[cnt] = 0; dp[i][1<<cnt] = 0; cnt++; } } int m; scanf("%d", &m); for(int i=1; i<=m; i++) { int u,v,w; scanf("%d%d%d", &u, &v, &w); mp[u].pb(pii(v, w)); mp[v].pb(pii(u, w)); } int all = (1 << cnt) - 1; for(int state = 0; state <= all; state++) { for(int i=1; i<=n; i++) { for(int s0 = (state-1) & state; s0; s0 = (s0-1) & state) { dp[i][state] = min(dp[i][state], dp[i][s0] + dp[i][state - s0]); } if(dp[i][state] < inf) que.push(i); } spfa(state); } for(int state=0; state<=all; state++) { g[state] = inf; if(check(state) == 0) continue; for(int i=1; i<=n; i++) { g[state] = min(g[state], dp[i][state]); } } int res2 = 0, ans = 0; for(int state = 0; state <= all; state ++) { if(check(state) == 0) continue; for(int s0 = (state - 1) & state; s0; s0 = (s0-1) & state) { if(check(s0) && check(state - s0)) { g[state] = min(g[state], g[s0] + g[state - s0]); } } if(cal(state) > res2) { res2 = cal(state); ans = g[state]; } else if(cal(state) == res2) { ans = min(ans, g[state]); } } printf("%d %d\n", res1 + res2, ans); for(int i=1; i<=n; i++) mp[i].clear(); } return 0; }
参考和学习: