大家应该都知道最小生成树问题吧
那么斯坦纳树问题就是在一张图上固定k个点,求一个最优子图使这k个点连通(其他的点可以不连通)
解决这类问题有一种通用的状压DP方法
设f[i][S]表示:当点i与这k个连通情况为S时,所花费的最小代价
那么有两种比较显然的转移
f[i][S]=f[i][T]+f[i][S^T] (T是S的子集,先转移,保证在第二步转移时的dp值是最优的)
f[u][S]=f[v][S]+dis[u][v] (v是u的临接点,后转移)
发现这样的时间复杂度为O((3^k)*n+(2^k)*SPFA(n,m))
(其实这种问题也比较板了)
现在,你已经学会了斯坦纳树,让我们来看一道例题
例题:
题解
(灵活运用)随机化+(灵活运用)斯坦纳树
随机选择k种颜色,然后把其他的颜色也随机地设为这k种颜色
现在的问题就变成了,求一个最小权值连通块,连通k种颜色(其实斯坦纳树并不一定非要状压关键点,它只是为状压DP提供了一种思路)
直接斯坦纳树就好了
这样随机化的准确率大概为k!/(k^k)
(就是对所有颜色做一个到k中颜色的映射,其中某k种颜色分配到的颜色互不相同的概率)
代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int INF=0x3f3f3f3f;
int dx[4]={1,-1,0,0},dy[4]={0,0,1,-1};
int n,m,K;
int a[20][20],ans,val[20][20],f[20][20][1<<7],id[3005],d[20][20],tmp[305];
bool vis[20][20];
struct node{int x,y,z;}q[1000005];
bool cmp(const node &x,const node &y){return x.z>y.z;}
int main()
{
freopen("graph.in","r",stdin);
freopen("graph.out","w",stdout);
srand(3993991);ans=INF;
int i,j,k,l,x,y,_x,_y,w;
scanf("%d%d%d",&n,&m,&K);
for(i=1;i<=n;i++)for(j=1;j<=m;j++){
scanf("%d",&a[i][j]);
if(a[i][j]!=-1)a[i][j]++;
}
for(i=1;i<=n;i++)for(j=1;j<=m;j++)scanf("%d",&val[i][j]);
int T=500,N=n*m;
for(i=1;i<=N;i++)id[i]=i;
for(i=0;i<=n+1;i++)d[i][0]=d[i][m+1]=-1;
for(j=0;j<=m+1;j++)d[0][j]=d[n+1][j]=-1;
while(T--){
random_shuffle(id+1,id+N+1);
for(i=1;i<=K;i++)tmp[id[i]]=i;
for(i=K+1;i<=N;i++)tmp[id[i]]=rand()%K+1;
for(i=1;i<=n;i++)for(j=1;j<=m;j++)d[i][j]=tmp[a[i][j]]-1;
int S=(1<<K);
memset(f,0x3f,sizeof(f));
for(i=1;i<=n;i++)for(j=1;j<=m;j++)
if(d[i][j]>=0)f[i][j][1<<d[i][j]]=val[i][j];
for(i=1;i<S;i++){
int he=0,ta=0;
for(l=(i-1)&i;l+l>i;l=(l-1)&i)
for(j=1;j<=n;j++)for(k=1;k<=m;k++)
f[j][k][i]=min(f[j][k][i],f[j][k][l]+f[j][k][i^l]-val[j][k]);
memset(vis,0,sizeof(vis));
for(j=1;j<=n;j++)for(k=1;k<=m;k++)if(f[j][k][i]<ans){
q[++ta].x=j;q[ta].y=k;
vis[j][k]=1;
}
while(he!=ta){
x=q[++he].x;y=q[he].y;
for(j=0;j<4;j++){
_x=x+dx[j];_y=y+dy[j];w=f[x][y][i]+val[_x][_y];
if(d[_x][_y]>=0&&w<ans&&w<f[_x][_y][i]){
f[_x][_y][i]=w;
if(!vis[_x][_y]){q[++ta].x=_x;q[ta].y=_y;vis[_x][_y]=1;}
}
}
vis[x][y]=0;
}
for(x=1;x<=n;x++)for(y=1;y<=m;y++)for(j=0;j<4;j++){
int _x=x+dx[j],_y=y+dy[j];w=f[x][y][i]+val[_x][_y];
if(d[_x][_y]>=0&&w<ans)
f[_x][_y][i|(1<<d[_x][_y])]=min(f[_x][_y][i|(1<<d[_x][_y])],w);
}
}
for(i=1;i<=n;i++)for(j=1;j<=m;j++)ans=min(ans,f[i][j][S-1]);
}
if(ans==INF)printf("-1\n");
else printf("%d",ans);
}