斯坦纳树DP
斯坦纳树经典DP方程:
f[s][i]
表示当前需连通点的连通情况至少为
s
,且路径上一定经过点
为什么说是至少为
s
,因为可能方案里的路径经过了别的点,然而在当前状态下我们只考察这
经典的两步转移:
f[s][i]=f[s′][i]+f[s−s′][i](无条件)
f[s][i]=f[s][j]+w[j][i](当且仅当j与i相连)
一个结论: f[s][i] 对应的方案的路径一定可以表示成一棵树的形态。
证明:显然如果路径上有一个环,我们总能拆掉环上一条边而代价不会更劣。
我们考虑一个状态 f[s][i] ,分三种情况来证明上述转移的正确性。
我们已经知道它最优方案是一棵树,假设这棵树在根的部位分叉了,则显然可以从分叉的(两个)不同的子集转移过来,即第一个转移就能满足要求。
否则这棵树在根的地方不分叉,而在底下分叉了,则我们可以通过在底下分叉的那个点的DP值,通过第二个转移得到答案。
还有一种情况是这棵树就是一条链,或者只是一个点。要么这是初始化的时候,这样显然正确。否则就是所有连通点在一条链上,那我们就能根据从 i <script type="math/tex" id="MathJax-Element-62">i</script>往下第一个连通点的DP值,同样通过第二个转移得到答案。
没有别的情况了,所以这个转移正确。
然后这题随便搞搞就行了。
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 10005
using namespace std;
namespace runzhe2000
{
const int S = N-1, INF = 1<<29;
int n, m, d, ecnt, last[N], f[1<<8][N], dis[N], vis[N], g[1<<4];
struct edge{int next, to, val;}e[N<<2];
void addedge(int a, int b, int c)
{
e[++ecnt] = (edge){last[a], b, c};
last[a] = ecnt;
}
struct heap
{
int x, d;
bool operator < (const heap &that) const {return d < that.d;}
}h[N<<1]; int tot;
void heap_insert(int x, int d)
{
h[++tot] = (heap){x, d};
for(int i = tot, j = i >> 1; j; i = j, j >>= 1)
{
if(h[i] < h[j]) swap(h[i], h[j]);
else break;
}
}
void heap_pop()
{
h[1] = h[tot--];
for(int i = 1, j = i << 1; j <= tot; i = j, j <<= 1)
{
if(h[j|1] < h[j]) j |= 1;
if(h[j] < h[i]) swap(h[i], h[j]);
else break;
}
}
void dijk()
{
memset(dis, 63, sizeof(dis)); memset(vis, 0, sizeof(vis));
dis[S] = 0; tot = 0; heap_insert(S, dis[S] = 0);
for(; tot; )
{
int x = h[1].x; heap_pop();
if(vis[x]) continue; vis[x] = 1;
for(int i = last[x]; i; i = e[i].next)
{
int y = e[i].to;
if(dis[x] + e[i].val < dis[y])
heap_insert(y, dis[y] = dis[x] + e[i].val);
}
}
}
void main()
{
scanf("%d%d%d",&n,&m,&d);
for(int i = 1, x, y, z; i <= m; i++)
{
scanf("%d%d%d",&x,&y,&z);
addedge(x, y, z);
addedge(y, x, z);
}
int tmpecnt = ecnt; memset(f, 63, sizeof(f));
for(int i = 1; i <= n; i++) if(i <= d || n-d+1 <= i)
{
ecnt = tmpecnt; last[S] = 0; addedge(S, i, 0); dijk();
int s = (i <= d) ? 1<<(i-1) : 1<<(d+n-i);
for(int j = 1; j <= n; j++)
f[s][j] = dis[j];
}
memset(g, 63, sizeof(g));
for(int sta = 2, _sta = 1<<(d*2); sta < _sta; sta++)
{
ecnt = tmpecnt; last[S] = 0;
for(int i = 1; i <= n; i++)
{
for(int s = (sta-1)&sta; s; s = (s-1)&sta)
f[sta][i] = min(f[sta][i], f[s][i] + f[sta-s][i]);
addedge(S, i, f[sta][i]);
}
dijk();
int s = sta & (sta>>d);
for(int i = 1; i <= n; i++)
{
f[sta][i] = dis[i];
g[s] = min(g[s], dis[i]);
}
}
for(int sta = 2, _sta = 1<<d; sta < _sta; sta++)
for(int s = (sta-1)&sta; s; s = (s-1)&sta)
g[sta] = min(g[sta], g[s] + g[sta - s]);
printf("%d\n",g[(1<<d)-1] < INF ? g[(1<<d)-1] : -1);
}
}
int main()
{
runzhe2000::main();
}