S1(P3) 最小斯坦纳树

最小斯坦纳树

从问题来入手定义,即为:

给一个图(暂定无向),再给一些图中的点,求最小的子图使得所有给定点连通。


不难发现实际上最后所求的应该是一棵树,因为环显然是多余的。

但是这个问题显然比最小生成树难不少。

这是个NP-Hard问题,所以只能求小范围内的数据。

所以点数100,边数500,所求点数10。

考虑状压DP。

Step 1 设计方程

\(f[i][S]\) 表示以 \(i\) 为根节点,已经包含了所求点集合状压后的子集 \(S\) 的最小花费,于是就有很神奇的思考:

  1. \(f[i][S]=min(f[i][T]+f[i][S ~xor ~T])\)
  2. \(f[i][S]=min(f[j][S]+dis[i][j])\)

这是在以根节点入度来对状态分类,从而使得转移方法可行。关于斯坦纳是怎么想到的,我就不知道了,反正......

含义呢:

  1. 多入度根时,将不同根的方案凑起来
  2. 如果根的入度为 \(1\) ,那么就把相邻的根的状态移过来

然后考虑怎么转移:

  1. 简单的二进制枚举
  2. 符合三角不等式,考虑最短路

关于 \(2\) 为什么要跑最短路,因为如果直接 \(for\) 一遍更新是不得行的,想想怎么求最短路的。不过特殊的是这个最短路是有可能为多源最短路的,而且不能用 \(Floyd\) ,因为更新的是 \(f[?][S]\)。这里要理解。或者可以不把它当成最短路,而是当成一种扩散,从花费最少的点慢慢扩散开,然后传递到其他的点上。对应就是最短路。

还有状态枚举顺序,直接从小到大枚举就好了,因为子集从小到大,一定是顺推过去的。

const int N=505,K=11;

int n,m,k,S[K],head[N],cnt,f[N][(1<<K)+11];

struct Edge{
  int fo,to,nxt,val;
}edge[N<<1];

inline void Add(int fo,int to,int val){
  edge[++cnt]={fo,to,head[fo],val};
  head[fo]=cnt;
}

priority_queue<pair<int,int>> pq;
inline void dj(int s){
	while(pq.size()){
		int v=-pq.top().first,x=pq.top().second;
    pq.pop();
    for(int i=head[x];i;i=edge[i].nxt){
      int &to=edge[i].to;
      if(f[to][s]>f[x][s]+edge[i].val){
        f[to][s]=f[x][s]+edge[i].val;
        pq.push({-f[to][s],to});
      }
    }
	}
}

inline void solve(){
  for(int s=1;s<(1<<k);++s){
    for(int i=1;i<=n;++i){
      for(int t=s&(s-1);t;t=s&(t-1))
        f[i][s]=Min(f[i][s],f[i][t]+f[i][s^t]);
      if(f[i][s]<1e9) pq.push({-f[i][s],i});
    } dj(s);
  }

  int ans=inf;
  for(int i=1;i<=n;++i) ans=Min(ans,f[i][(1<<k)-1]);
  cout<<ans<<'\n';
}

signed main(){
  //freopen();
  //freopen();
  IOS
  
  cin>>n>>m>>k;
  for(int i=1,fo,to,val;i<=m;++i){
    cin>>fo>>to>>val;
    Add(fo,to,val);
    Add(to,fo,val);
  }
  for(int i=1;i<=k;++i) cin>>S[i];
  memset(f,0x3f,sizeof(f));
  for(int i=1;i<=k;++i)
    f[S[i]][(1<<(i-1))]=0;
  for(int i=1;i<=n;++i) f[i][0]=0;
  solve();

  return 0;
}

P4294 游览计划

非常显然的板子,但是需要注意的是边权变成了点权,所以转移的时候要改一点点。然后还要求要找路径,所以可以搞一个 \(pref\) 来保存转移方式。我用的是正数表最短路转移,负数表子集转移,注意如果是0千万记得要return,不然就会像个傻逼在那里调半天...

const int N=11;

vector<int> v;
int n,m,mp[N*N],f[N*N][(1<<N)+11];
inline int num(int i,int j)
{return (i-1)*m+j;}
int head[N*N],cnt;
struct Edge{
  int fo,to,nxt,val;
}edge[N*N<<2];

inline void Add(int fo,int to,int val){
  edge[++cnt]={fo,to,head[fo],val};
  head[fo]=cnt;
}

inline void pre(){
  memset(f,0x3f,sizeof(f));
  F(1,i,1,n) F(1,j,1,m){
    if(!mp[num(i,j)]){ v.push_back(num(i,j)); f[num(i,j)][1<<(v.size()-1)]=0;}
    for(int t=0;t<4;++t){
      int tx=dx[t]+i,ty=dy[t]+j;
      if(tx<1||ty<1||tx>n||ty>m) continue;
      Add(num(i,j),num(tx,ty),mp[num(tx,ty)]);
    }
    f[num(i,j)][0]=0;
  }
}

char ans[N*N];
priority_queue<pair<int,int>> pq;
int pref[N*N][(1<<N)+11];//dp前驱存储(如果不是在DJ中更新,则为0(dfs时采取二进制爆搜))

inline void dj(int s){
  while(pq.size()){
    int v=-pq.top().first,x=pq.top().second; pq.pop();
    for(int i=head[x];i;i=edge[i].nxt){
      int &to=edge[i].to,val=edge[i].val;
      if(f[to][s]>f[x][s]+val){
        f[to][s]=f[x][s]+val;
        pref[to][s]=x;
        pq.push({-f[to][s],to});
      }
    }
  }
}

inline void dfs(int pos,int val){
  if(!val) return ;
  ans[pos]='o';
  if(!pref[pos][val]) return ;
  else if(pref[pos][val]>0) dfs(pref[pos][val],val);
//   else for(int t=val&(val-1);t;t=(t-1)&val)
//     if(f[pos][t]+f[pos][val^t]-mp[pos]==f[pos][val])
//       dfs(pos,t),dfs(pos,t^val);
  else dfs(pos,-pref[pos][val]),dfs(pos,(-pref[pos][val])^val);
}

inline void solve(){
  int all=(1<<v.size())-1;
  for(int s=0;s<=all;++s){
    for(int i=1;i<=n*m;++i){
      for(int t=s&(s-1);t;t=(t-1)&s){
//         f[i][s]=Min(f[i][s],f[i][t]+f[i][s^t]-mp[i]);
        if(f[i][s]>f[i][t]+f[i][s^t]-mp[i])
          f[i][s]=f[i][t]+f[i][s^t]-mp[i],pref[i][s]=-t;
      }
      if(f[i][s]<2e8) pq.push({-f[i][s],i});
    } dj(s);
  }//okey

  int mn=inf,res=0;
  for(int i=1;i<=n*m;++i)
    if(f[i][all]<mn) mn=f[i][all],res=i;
  
  dfs(res,all);
  for(int i=1;i<=n*m;++i){
    if(mp[i]==0) ans[i]='x';
    else if(!ans[i]) ans[i]='_';
  }

  cout<<mn<<'\n';
  for(int i=1;i<=n;++i,cout<<'\n')
    for(int j=1;j<=m;++j)
      cout<<ans[num(i,j)];
}

signed main(){
  //freopen();
  //freopen();
  IOS
  //int T;
  cin>>n>>m;
  F(1,i,1,n) F(1,j,1,m) cin>>mp[num(i,j)];
  pre();
  solve();
  
  return 0;
}

· EOF

posted @ 2023-08-20 09:38  ComplexityMFC  阅读(7)  评论(0编辑  收藏  举报