传送门:bzoj2595
题解
法1:斯坦纳树
设 g [ i ] g[i] g[i]表示点 i i i的价值。
状压DP,设 f [ i ] [ m a s k ] f[i][mask] f[i][mask]表示以 i i i点为根的连通覆盖的景点集合为 m a s k mask mask时的最小花费。
枚举
m
a
s
k
mask
mask的子集
s
s
s:
f
[
i
]
[
m
a
s
k
]
=
m
i
n
(
f
[
i
]
[
s
]
+
f
[
i
]
[
m
a
s
k
−
s
]
−
g
[
i
]
)
f[i][mask]=min(f[i][s]+f[i][mask-s]-g[i])
f[i][mask]=min(f[i][s]+f[i][mask−s]−g[i])
同时
f
[
i
]
[
m
a
s
k
]
=
m
i
n
(
f
[
j
]
[
m
a
s
k
]
+
g
[
i
]
)
f[i][mask]=min(f[j][mask]+g[i])
f[i][mask]=min(f[j][mask]+g[i]),这个可以
s
p
f
a
spfa
spfa一起求。
#include<bits/stdc++.h>
using namespace std;
const int N=11,M=(1<<10)+10,inf=0x3f3f3f3f;
int n,m,q,a[N][N],f[N][N][M];
int s,S;
bool inq[N][N],vs[N][N];
struct Q{int x,y,z;}pre[N][N][M];
struct P{int x,y;};
queue<P>que;
int dx[5]={-1,1,0,0};
int dy[5]={0,0,-1,1};
inline void spfa()
{
int i,v,ix,iy,x,y;
for(;que.size();){
ix=que.front().x;iy=que.front().y;que.pop();
v=f[ix][iy][s];
for(i=0;i<4;++i){
x=ix+dx[i];y=iy+dy[i];
if(x<1 || x>n || y<1 || y>m || f[x][y][s]<=v+a[x][y]) continue;
f[x][y][s]=v+a[x][y];pre[x][y][s]=(Q){ix,iy,s};
if(inq[x][y]) continue;inq[x][y]=true;que.push((P){x,y});
}
inq[ix][iy]=false;
}
}
void dfs(int x,int y,int z)
{
vs[x][y]=true;
Q tp=pre[x][y][z];if(!tp.x) return;
dfs(tp.x,tp.y,tp.z);
if((tp.x==x)&&(tp.y==y)) dfs(x,y,z-tp.z);
}
int main(){
int i,j,t,res,mx,my;
memset(f,0x3f,sizeof(f));
scanf("%d%d",&n,&m);
for(i=1;i<=n;++i)
for(j=1;j<=m;++j){
scanf("%d",&a[i][j]);
if(!a[i][j]) {f[i][j][1<<q]=0;q++;mx=i;my=j;}
}
if(q<2){putchar('0');return 0;}
S=(1<<q)-1;
for(s=1;s<=S;++s){
for(i=1;i<=n;++i)
for(j=1;j<=m;++j){
for(t=s&(s-1);t;t=s&(t-1)){
res=f[i][j][t]-a[i][j]+f[i][j][s-t];
if(res>=f[i][j][s]) continue;
f[i][j][s]=res;pre[i][j][s]=(Q){i,j,t};
}
if(f[i][j][s]<inf) que.push((P){i,j}),inq[i][j]=true;
}
spfa();
}
printf("%d\n",f[mx][my][S]);
dfs(mx,my,S);
for(i=1;i<=n;++i){
for(j=1;j<=m;++j){
if(!a[i][j]) putchar('x');
else if(vs[i][j]) putchar('o');
else putchar('_');
}
puts("");
}
return 0;
}
法2:插头DP
想了很久,发现括号匹配和独立插头很不好表示。因为要求的是联通块,转移时单双插头可以任意切换,所以用最小表示法最适合。
如何限制来满足题意呢?
景点必须有插头。
对于不是景点的位置,若它上方有插头且上方的联通块没有其它插头可以延伸时,它必须要选,否则可以不选。
当已经处理完所有景点位置后,每个联通块不超过1的状态可用于更新答案。
有点难打,码了好久。
#include<bits/stdc++.h>
using namespace std;
const int mod=23333,N=11,M=1e6+10,inf=0x3f3f3f3f;
int n,m,q,g[N][N],pr,pt=1,mx,my;
int pbs,bs,ans=inf,lst,pre[M],vs[10],vp[10];
bool fw[N][N];
struct P{int x,y,z;}ifo[M];
struct Hs{
int val[mod],key[mod],sz,hs[mod];
inline void itia(){
memset(val,0x3f,sizeof(val));memset(hs,0,sizeof(hs));
memset(key,0xff,sizeof(key));sz=0;
}
inline void nh(int u,int v){hs[u]=++sz;key[sz]=v;}
inline int pls(int S){
for(int i=S%mod;;i=(i+1==mod)?0:(i+1)){
if(!hs[i]) nh(i,S);
if(key[hs[i]]==S) return hs[i];
}
}
}f[2];
inline int gt(int S,int pos){return (!pos)?0:((S>>((pos-1)*3))&7);}
inline void sett(int &S,int pos,int v)
{pos=(pos-1)*3;S|=(7<<pos);S^=(7<<pos);S|=(v<<pos);}
inline int fd(int S,int x)
{
int i,re=0;
for(i=1;i<=m;++i,S>>=3) re+=((S&7)==x);
return re;
}
inline int trs(int S)
{
memset(vs,0xff,sizeof(vs));
int i,re=0,cot=0;vs[0]=0;
for(i=1;i<=m;++i,S>>=3) vp[i]=(S&7);
for(i=m;i;--i){
if(vs[vp[i]]==-1) vs[vp[i]]=++cot;
re=(re<<3)|(vs[vp[i]]);
}
return re;
}
inline void calans()
{
int i,S,vl;
for(int k=f[pr].sz;k;--k){
S=f[pr].key[k];vl=f[pr].val[k];
if(ans<=vl) continue;
for(i=1;i<=m;++i,S>>=3) if((S&7)>1) break;
if(i<=m) continue;
ans=vl;lst=bs+k;
}
}
inline void cal(int x,int y)
{
int i,k,S,vl,a,b,id,res;
pr^=1;pt^=1;f[pr].itia();pbs=bs;bs+=f[pt].sz;
for(k=f[pt].sz;k;--k){
S=f[pt].key[k];vl=f[pt].val[k];
a=gt(S,y-1);b=gt(S,y);
if(((g[x][y]!=0)&&((!b)||(fd(S,b)>1)))){
res=S;sett(res,y,0);id=f[pr].pls(trs(res));
if(vl<f[pr].val[id]){
ifo[bs+id]=(P){x,y,0};
f[pr].val[id]=vl;pre[bs+id]=pbs+k;
}
}
if((!a)&&(!b)) sett(S,y,7);
else if(a&&(!b)) sett(S,y,a);
else if(a&&b&&(a!=b)){
for(res=S,i=1;i<=m;++i,res>>=3)
if((res&7)==a) sett(S,i,b);
}
id=f[pr].pls(trs(S));
if(vl+g[x][y]>=f[pr].val[id]) continue;ifo[bs+id]=(P){x,y,1};
f[pr].val[id]=vl+g[x][y];pre[bs+id]=pbs+k;
}
}
int main(){
int i,j;
scanf("%d%d",&n,&m);
for(i=1;i<=n;++i)
for(j=1;j<=m;++j){
scanf("%d",&g[i][j]);
if(!g[i][j]) mx=i,my=j,q++;
}
if(q<2){putchar('0');return 0;}
f[0].itia();i=f[0].pls(0);f[0].val[i]=0;
for(i=1;i<=n;++i){
for(j=1;j<=m;++j){
cal(i,j);
if((i>mx)||(i==mx&&j>=my)) calans();
}
}
printf("%d\n",ans);
for(i=lst;i>1;i=pre[i]) if(ifo[i].z) fw[ifo[i].x][ifo[i].y]=1;
for(i=1;i<=n;++i){
for(j=1;j<=m;++j){
if(!g[i][j]) putchar('x');
else if(fw[i][j]) putchar('o');
else putchar('_');
}
puts("");
}
return 0;
}