斯坦纳树是求在树上使得一部分点集联通的子树,最小斯坦纳树就是在此前提下最小化子树的权值之和.
通常就是在动态规划中以二进制表示联通状态进行求解,由于状态转移方程的形式跟spfa求解的方式相似度很高,所以可以利用spfa进行状态转移.
感觉题目的模型还是比较容易看出来的,有些题目进行了简单的变形,如求斯坦纳树森林,或者联通点分为两类,对两类点的数量一定的要求.
A - Peach Blossom Spring HDU - 4085
k个房子k个庇护所,每一座房子都必须可以和一个庇护所联通.
由于不是所有要求点都需要联通,所以题目就是要求斯坦纳森林,但是由于上述限制条件,合法的状态必须是房子数量和庇护所数量相同.
最终树合并成森林时对状态判断一下合法性就行了.
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef unsigned long long ull;
#define fi first
#define se second
#define mp make_pair
#define pb push_back
const int maxn=65;
const int maxm=1<<10;
const int maxe=2015;
const int inf=0x3f3f3f3f;
const int seed=1e9+7;
int n,m,k,nn;
struct edge{int to,nxt,len;}e[maxe];
int head[maxn],tot;
void adde(int u,int v,int l){e[tot].to=v;e[tot].len=l;e[tot].nxt=head[u];head[u]=tot++;}
int d[maxn][maxm];
int dp[maxm];
int s[maxn];
void init(){
tot=0;
memset(head,-1,sizeof(head));
memset(s,0,sizeof(s));
scanf("%d%d%d",&n,&m,&k);
nn=1<<(2*k);
for(int i=1;i<=n;i++){
for(int j=0;j<nn;j++)d[i][j]=inf;
}
int a,b,c;
for(int i=0;i<m;i++){
scanf("%d%d%d",&a,&b,&c);
adde(a,b,c);adde(b,a,c);
}
for(int i=1;i<=k;i++){
s[i]=1<<(i-1);d[i][s[i]]=0;
s[n-i+1]=1<<(k+i-1);d[n-i+1][s[n-i+1]]=0;
}
}
queue<pii> que;
bool inque[maxn][maxm];
inline bool check(int msk){
int v1=0,v2=0;
for(int i=0;i<k;i++)if(msk>>i&1)v1++;
for(int i=k;i<2*k;i++)if(msk>>i&1)v2++;
return v1==v2;
}
inline bool update(int x,int y,int w){
if(w<d[x][y]){d[x][y]=w;return 1;}
return 0;
}
void spfa(){
while(!que.empty()){
int x=que.front().fi,y=que.front().se;que.pop();
for(int i=head[x];i!=-1;i=e[i].nxt){
int v=e[i].to,w=e[i].len;
if(update(v,y|s[v],d[x][y]+w)&&y==(y|s[v])&&!inque[v][y]){
inque[v][y]=1;que.push(mp(v,y));
}
}
inque[x][y]=0;
}
}
void work(){
for(int msk=0;msk<nn;msk++){
for(int x=1;x<=n;x++){
for(int i=(msk-1)&msk;i;i=(i-1)&msk){
d[x][msk]=min(d[x][msk],d[x][i|s[x]]+d[x][(msk-i)|s[x]]);
}
if(d[x][msk]<inf){que.push(mp(x,msk));inque[x][msk]=1;}
}
spfa();
}
}
int main(){
int T;
scanf("%d",&T);
while(T--){
init();
work();
for(int j=1;j<nn;j++){
dp[j]=inf;
for(int i=1;i<=n;i++)dp[j]=min(dp[j],d[i][j]);
}
for(int i=1;i<nn;i++){
if(check(i)){
for(int j=(i-1)&i;j;j=(j-1)&i){
if(check(j)){
dp[i]=min(dp[i],dp[j]+dp[i-j]);
}
}
}
}
if(dp[nn-1]>=inf)printf("No solution\n");
else printf("%d\n",dp[nn-1]);
}
return 0;
}
B - Dig The Wells HDU - 3311
要求使得所有要求点都有水,有水的方法可以是在这个点上挖井,也可以是通过道路从其他点引过来.
不需要全部联通,只需要每个斯坦纳树内的都有水即可.
由于可以在任意一个点上挖井,所有初始化dp的时候特殊处理以下即可.
可以在二进制状态上多加一位表示联通块内有没有挖井就可以了.
最终树合并森林的时候要求两个子状态都是有挖井.
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef unsigned long long ull;
#define fi first
#define se second
#define mp make_pair
#define pb push_back
const int maxn=1100+15;
const int maxm=1<<6;
const int maxe=1e4+15;
const int inf=0x3f3f3f3f;
const int seed=1e9+7;
int n,m,p,nn;
struct edge{
int to,nxt,len;
}e[maxe];
int tot;
int head[maxn];
void adde(int u,int v,int l){
e[tot].to=v;e[tot].len=l;e[tot].nxt=head[u];head[u]=tot++;
}
int s[maxn];
int dp[maxn][maxm];
int a,b,c;
void init(){
nn=1<<(n+1);
for(int i=1;i<=n+m;i++){
for(int j=0;j<nn;j++){
dp[i][j]=inf;
}
s[i]=0;
}
for(int i=1;i<=n;i++){
scanf("%d",&a);
dp[i][1<<i]=0;
dp[i][(1<<i)+1]=a;
s[i]=(1<<i);
}
for(int i=1;i<=m;i++){
scanf("%d",&a);
dp[i+n][0]=0;
dp[i+n][1]=a;
}
for(int i=0;i<p;i++){
scanf("%d%d%d",&a,&b,&c);
adde(a,b,c);
adde(b,a,c);
}
}
queue<pii> que;
bool inque[maxn][maxm];
void spfa(){
while(!que.empty()){
pii cnt=que.front();que.pop();
int u=cnt.fi;
int sta=cnt.se;
for(int i=head[u];i!=-1;i=e[i].nxt){
int v=e[i].to;
int l=e[i].len;
if(dp[u][sta]+l<dp[v][sta|s[v]]){
dp[v][sta|s[v]]=dp[u][sta]+l;
if((sta|s[v])==sta&&!inque[v][sta]){
inque[v][sta]=1;
que.push(mp(v,sta));
}
}
}
inque[u][sta]=0;
}
}
void stantree(){
for(int j=0;j<nn;j++){
for(int i=1;i<=n+m;i++){
for(int k=j&(j-1);k;k=j&(k-1)){
dp[i][j]=min(dp[i][j],dp[i][k|s[i]]+dp[i][j-k|s[i]]);
}
if(dp[i][j]<inf){
que.push(mp(i,j));inque[i][j]=1;
}
}
spfa();
}
}
int ansdp[maxn];
int main(){
while(~scanf("%d%d%d",&n,&m,&p)){
tot=0;memset(head,-1,sizeof(head));
init();
stantree();
for(int j=0;j<nn;j++){
ansdp[j]=inf;
for(int i=1;i<=n+m;i++){
ansdp[j]=min(ansdp[j],dp[i][j]);
}
}
for(int j=0;j<nn;j++){
if(j&1)
for(int k=j&(j-1);k;k=(k-1)&j){
if(k&1){
int cc=j-k|1;
ansdp[j]=min(ansdp[j],ansdp[k]+ansdp[cc]);
}
}
}
printf("%d\n",ansdp[nn-1]);
}
return 0;
}
C - Ticket to Ride POJ - 3123
给出树并且要让树上的四个点对联通
本质上没什么差别,还是斯坦纳森林.
只不过合并森林是合法判断要改变以下就可以了.
#include<cstdio>
#include<map>
#include<algorithm>
#include<cstring>
#include<queue>
#include<string>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef unsigned long long ull;
#define fi first
#define se second
#define mp make_pair
#define pb push_back
const int maxn=3000+15;
const int maxm=1<<8;
const int maxe=2000+15;
const int inf=0x3f3f3f3f;
const int seed=1e9+7;
int n,m,p,nn;
struct edge{
int to,nxt,len;
}e[maxe];
int tot;
int head[maxn];
void adde(int u,int v,int l){
e[tot].to=v;e[tot].len=l;e[tot].nxt=head[u];head[u]=tot++;
}
int s[maxn];
int dp[maxn][maxm];
int a,b,c,cc;
int ansdp[maxn];
//char str[50];
char str[50];
map<string,int> ma;
void init(){
nn=1<<8;
for(int i=1;i<=n;i++){
for(int j=0;j<nn;j++){
dp[i][j]=inf;
}
s[i]=0;
}
for(int i=0;i<m;i++){
scanf("%s",str);
int id1=ma[str];
scanf("%s",str);
int id2=ma[str];
scanf("%d",&c);
adde(id1,id2,c);
adde(id2,id1,c);
}
for(int i=0;i<4;i++){
scanf("%s",str);
int id1=ma[str];
s[id1]|=1<<i;
scanf("%s",str);
int id2=ma[str];
s[id2]|=1<<(7-i);
dp[id1][s[id1]]=0;
dp[id2][s[id2]]=0;
}
}
queue<pii> que;
bool inque[maxn][maxm];
void spfa(){
while(!que.empty()){
pii cnt=que.front();que.pop();
int u=cnt.fi;
int sta=cnt.se;
for(int i=head[u];i!=-1;i=e[i].nxt){
int v=e[i].to;
int l=e[i].len;
if(dp[u][sta]+l<dp[v][sta|s[v]]){
dp[v][sta|s[v]]=dp[u][sta]+l;
if((sta|s[v])==sta&&!inque[v][sta]){
inque[v][sta]=1;
que.push(mp(v,sta));
}
}
}
inque[u][sta]=0;
}
}
void stantree(){
for(int j=0;j<nn;j++){
for(int i=1;i<=n;i++){
for(int k=j&(j-1);k;k=j&(k-1)){
dp[i][j]=min(dp[i][j],dp[i][k|s[i]]+dp[i][j-k|s[i]]);
}
if(dp[i][j]<inf){
que.push(mp(i,j));inque[i][j]=1;
}
}
spfa();
}
}
bool check(int sta){
for(int i=0;i<4;i++){
if(sta>>i&1){
if(sta>>(7-i)&1)continue;
return 0;
}
else{
if(sta>>(7-i)&1)return 0;
}
}
return 1;
}
int main(){
while(~scanf("%d%d",&n,&m),n+m){
tot=0;memset(head,-1,sizeof(head));
ma.clear();
for(int i=1;i<=n;i++){
scanf("%s",str);
ma[str]=i;
}
init();
stantree();
for(int j=0;j<nn;j++){
ansdp[j]=inf;
for(int i=1;i<=n;i++){
ansdp[j]=min(ansdp[j],dp[i][j]);
}
}
for(int j=0;j<nn;j++){
if(check(j)){
for(int k=j&(j-1);k;k=(k-1)&j){
if(check(k)){
ansdp[j]=min(ansdp[j],ansdp[k]+ansdp[j-k]);
}
}
}
}
printf("%d\n",ansdp[nn-1]);
}
return 0;
}
E - 游览计划 HYSBZ - 2595
经典题目.不是树了,变成了四联通的二维矩阵.
由于权值不再是边了,而变成点,所以合并的时候要考虑是不是会重复计算一个点的点权两次.
记录路径也是要特殊处理一下.
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef unsigned long long ull;
#define fi first
#define se second
#define mp make_pair
#define pb push_back
const int maxn=10;
const int maxm=1<<10;
const int maxe=2015;
const int inf=0x3f3f3f3f;
const int seed=1e9+7;
int n,m,k,nn;
struct node{
int x,y,sta;
node(){}
node(int a,int b,int c){
x=a;y=b;sta=c;
}
};
int ma[maxn][maxn];
int s[maxn][maxn];
int dp[maxn][maxn][maxm];
bool inque[maxn][maxn][maxm];
pair<node,node> g[maxn][maxn][maxm];
char ansmap[maxn][maxn];
void init(){
int tot=0;
for(int i=0;i<n;i++){
for(int j=0;j<m;j++){
if(ma[i][j]==0){s[i][j]=(1<<tot);tot++;}
else s[i][j]=0;
ansmap[i][j]='_';
if(ma[i][j]==0)ansmap[i][j]='x';
}
}
nn=1<<tot;
for(int i=0;i<n;i++)
for(int j=0;j<m;j++){
for(int k=0;k<nn;k++)dp[i][j][k]=inf;
if(s[i][j]){
dp[i][j][s[i][j]]=0;
g[i][j][s[i][j]].fi=node(-1,-1,-1);
g[i][j][s[i][j]].se=node(-1,-1,-1);
}
}
}
int dx[4]={0,0,1,-1};
int dy[4]={-1,1,0,0};
queue<node> que;
void spfa(){
while(!que.empty()){
node cnt=que.front();que.pop();
int x=cnt.x,y=cnt.y,sta=cnt.sta;
for(int i=0;i<4;i++){
int nx=x+dx[i],ny=y+dy[i];
if(nx<0||nx>=n||ny<0||ny>=m)continue;
int newsta=sta|s[nx][ny];
if(dp[x][y][sta]+ma[nx][ny]<dp[nx][ny][newsta]){
dp[nx][ny][newsta]=dp[x][y][sta]+ma[nx][ny];
g[nx][ny][newsta].fi=node(x,y,sta);
g[nx][ny][newsta].se=node(-1,-1,-1);
if(newsta==sta&&!inque[nx][ny][newsta]){
inque[nx][ny][newsta]=1;
que.push(node(nx,ny,newsta));
}
}
}
inque[x][y][sta]=0;
}
}
void work(){
for(int j=1;j<nn;j++){
for(int x=0;x<n;x++){
for(int y=0;y<m;y++){
for(int i=(j-1)&j;i;i=(i-1)&j){
if(dp[x][y][i]+dp[x][y][j-i]-ma[x][y]<=dp[x][y][j]){
dp[x][y][j]=dp[x][y][i]+dp[x][y][j-i]-ma[x][y];
g[x][y][j].fi=node(x,y,i);
g[x][y][j].se=node(x,y,j-i);
}
}
if(dp[x][y][j]<inf){que.push(node(x,y,j));inque[x][y][j]=1;}
}
}
spfa();
}
}
void dfs(int x,int y,int sta){
if(ma[x][y])ansmap[x][y]='o';
node lef=g[x][y][sta].fi;
node rig=g[x][y][sta].se;
if(lef.x!=-1)dfs(lef.x,lef.y,lef.sta);
if(rig.x!=-1)dfs(rig.x,rig.y,rig.sta);
}
int main(){
scanf("%d%d",&n,&m);
for(int i=0;i<n;i++){
for(int j=0;j<m;j++){
scanf("%d",&ma[i][j]);
}
}
init();
work();
int ans=inf;
int ansx,ansy;
for(int i=0;i<n;i++){
for(int j=0;j<m;j++){
if(dp[i][j][nn-1]<ans){
ans=dp[i][j][nn-1];
ansx=i;ansy=j;
}
}
}
printf("%d\n",ans);
dfs(ansx,ansy,nn-1);
for(int i=0;i<n;i++){
for(int j=0;j<m;j++){
putchar(ansmap[i][j]);
}
printf("\n");
}
return 0;
}
/*
10 10
0 0 0 0 0 0 0 0 0 0
1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1
*/