S1(P3) 最小斯坦纳树
最小斯坦纳树
从问题来入手定义,即为:
给一个图(暂定无向),再给一些图中的点,求最小的子图使得所有给定点连通。
不难发现实际上最后所求的应该是一棵树,因为环显然是多余的。
但是这个问题显然比最小生成树难不少。
这是个NP-Hard问题,所以只能求小范围内的数据。
所以点数100,边数500,所求点数10。
考虑状压DP。
Step 1 设计方程
令 \(f[i][S]\) 表示以 \(i\) 为根节点,已经包含了所求点集合状压后的子集 \(S\) 的最小花费,于是就有很神奇的思考:
- \(f[i][S]=min(f[i][T]+f[i][S ~xor ~T])\)
- \(f[i][S]=min(f[j][S]+dis[i][j])\)
这是在以根节点入度来对状态分类,从而使得转移方法可行。关于斯坦纳是怎么想到的,我就不知道了,反正......
含义呢:
- 多入度根时,将不同根的方案凑起来
- 如果根的入度为 \(1\) ,那么就把相邻的根的状态移过来
然后考虑怎么转移:
- 简单的二进制枚举
- 符合三角不等式,考虑最短路
关于 \(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