【模板】斯坦纳树

模拟赛出了一个斯坦纳森林的题,全军覆没。

于是赶紧过来学习。


1.概述

先来看这道题:一张无向图,求它的最小生成树。

不会有人不会吧,Kruskal或者Prim一下就好了。

然后是它的进阶版:

一张无向图 G|V|100,|E|500,给定一个大小为 k(k10) 集合 S,求一个子图 G(V,E),使得 SV,G 连通,且最小化 E 权值和。

可以发现最小生成树就是 S=V 时的特殊情况。那么这个东西怎么做呢?

2.解法

首先,G 一定是一颗树。

要不然如果有环的话随便断掉一条边还是符合的。

于是我们钦定一个树根,设 dp[i][mask] 表示以 i 为根的一棵树,包含了 mask 中所有点的最小权值(mask 是状压 S 中的点,所以 i可能不在 mask 中)。

然后转移的时候分两种情况:

  1. 树中 i 的度不为 1。

那么一定可以将 mask 分为两个子集 s,masks,使得它们代表的集合分别在 i 的两个不同的子树中。

但我们不知道 s 是什么,于是我们考虑暴力,用 O(3k) 的复杂度枚举子集,暴力转移。

所以有

dp[i][mask]=minsmaskdp[i][s]+dp[i][masks]

  1. 树中i 的度为 1。

那么这时与 i 相邻的那个点 j 的答案加上 dis(i,j) 一定比 i 优。

但是我们仍然不知道 j 是那个点,也不知道哪些 i 度为 1,于是我们继续考虑暴力,用 O(n2) 的复杂度枚举两个点,暴力转移。

所以又有

dp[i][mask]=minj=1ndp[j][mask]+dis[i][j]

可以证明,只有 i,j 相邻时 dp[i][mask] 会改变。

upd: 好像有点问题。建议不要管这个暴力,直接看下面最短路的方法 😃

于是做完了。

注意事项:

dis[i][j] 的求法可以 O(n3)Floyd

总复杂度 O(n3+n2×2k+n×3k)

但是我们还有一种做法。

有些恶心的出题壬会把 n 开到 1000。这时怎么办呢?

观察第二种情况的式子:

dp[i][mask]=minj=1ndp[j][mask]+dis[i][j]

既然要优化,我们就限制一下 j 的范围:

dp[i][mask]=minj=1,jindp[j][mask]+dis[i][j]

然后我们发现:

这和最短路的松弛操作长的一模一样

于是我们就可以愉快的用 dijkstraSPFA(只有正权)优化了。

总复杂度 O(nlogn×2k+n×3k)

例题:

1.【模板】最小斯坦纳树

就是板子。

n 只有 100,用Floyd

Code:

// Problem: P6192 【模板】最小斯坦纳树
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P6192
// Memory Limit: 250 MB
// Time Limit: 1000 ms


#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define f(i,a,b) for(ll i=a;i<=b;i++)
#define wt int tt=d;while(tt--)
#define py puts("Yes")
#define pn puts("No")
#define fe(i,e) for(int i=0;i<e.size();i++)
#define vi vector<ll>
inline ll rd() {
	ll x=0,f=1;
	char c=getchar();
	while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
	while(isdigit(c))x=x*10+c-'0',c=getchar();
	return x*f;
}
#define d rd()
#define pb push_back
const ll N=300010;
ll dis[110][110];
ll qp(ll a,ll b,ll p){
	ll ans=1;while(b){
		if(b&1)ans=ans*a%p;
		a=a*a%p;b>>=1;
	}return ans;
}ll n,m,k;
ll s,dp[110][2010];
int main(){
	memset(dis,0x3f,sizeof(dis));
	n=d,m=d,k=d;
	f(i,1,m){
		ll u=d,v=d,w=d;
		dis[u][v]=min(dis[u][v],w);
		dis[v][u]=min(dis[v][u],w);
	}f(kk,1,n)f(i,1,n)f(j,1,n)dis[i][j]=min(dis[i][j],dis[i][kk]+dis[kk][j]);
	memset(dp,0x3f,sizeof(dp));
	f(i,1,k)s=d,dp[s][1<<(i-1)]=0;
	f(msk,0,(1<<k)-1){
		f(i,1,n)for(int ms=msk;ms;ms=(ms-1)&msk)dp[i][msk]=min(dp[i][msk],dp[i][ms]+dp[i][msk^ms]);
		f(i,1,n)f(j,1,n)dp[i][msk]=min(dp[i][msk],dp[j][msk]+dis[i][j]);
	}printf("%lld\n",dp[s][(1<<k)-1]);
	
	return 0;
}

2.Ticket to Ride

题意:一张图,给定 4 对关键点(一共 8 个点),求最小斯坦纳森林。

发现按照上面的那样做不行,因为这个不用保证所有点连通。

因为所有的点都是按照顺序读进来的,所以只有第一对点在 mask 中连通表示为 (00000011)2,只有第二对点表示为 (00001100)2,第一对和第二对同时连通为 (00001111)2

所以只有 mask[0](表示 mask 的第 0 位)=mask[1],mask[2]=mask[3].mask[4]=mask[5],mask[6]=mask[7] 时这个 mask 合法。

然后就可以写一个 check 来判断是否合法。

我们再设一个数组 f[mask],表示合法的 mask 状态的点对分别连通时的最小权值。

所以一开始 f[mask]=mini=1ndp[i][mask]

假设 mask=(11001111)2,所以

f[11001111]=min(f[11000000]+f[00001111],f[11001100]+f[00000011],f[11000011]+f[00001100])

于是可以发现

f[mask]=minsmasksf[s]+f[masks]

于是又是一个枚举子集。

然后好了。

Code:

// Problem: Ticket to Ride
// Contest: POJ - Northwestern Europe 2006
// URL: http://poj.org/problem?id=3123
// Memory Limit: 65 MB
// Time Limit: 2000 ms

#include<cstring>
#include <algorithm>
#include <bitset>
#include <complex>
#include <deque>
#include <exception>
#include <fstream>
#include <functional>
#include <iomanip>
#include <ios>
#include <iosfwd>
#include <iostream>
#include <istream>
#include <iterator>
#include <limits>
#include <list>
#include <locale>
#include <map>
#include <memory>
#include <new>
#include <numeric>
#include <ostream>
#include <queue>
#include <set>
#include <sstream>
#include <stack>
#include <stdexcept>
#include <streambuf>
#include <string>
#include <typeinfo>
#include <utility>
#include <valarray>
#include <vector>
using namespace std;
#define ll long long
#define f(i,a,b) for(ll i=a;i<=b;i++)
#define wt int tt=d;while(tt--)
#define py puts("Yes")
#define pn puts("No")
#define fe(i,e) for(int i=0;i<e.size();i++)
#define vi vector<ll>
inline ll rd() {
	ll x=0,f=1;
	char c=getchar();
	while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
	while(isdigit(c))x=x*10+c-'0',c=getchar();
	return x*f;
}
#define d rd()
#define pb push_back
const ll N=300010;
struct edge{ll v,w,nx;}e[N<<1];
ll hd[N],cnt;
void add(ll u,ll v,ll w){e[++cnt]=(edge){v,w,hd[u]};hd[u]=cnt;}
ll qp(ll a,ll b,ll p){
	ll ans=1;while(b){
		if(b&1)ans=ans*a%p;
		a=a*a%p;b>>=1;
	}return ans;
}ll n,m,k;
string s,t;
ll dis[50][50],ans;
map<string,ll>mp;
ll dp[50][500];
ll dpp[500];
bool ch(ll x){
	f(i,1,4){
		ll p=x-(x>>2<<2);
		if(p==3||p==0);
		else return 0;
		x>>=2;
	}return 1;
}
int main(){
	while(1){
		n=d,m=d,k=8;if(n+m==0)break;mp.clear();
		f(i,1,n)f(j,1,n)dis[i][j]=9999999;
		f(i,1,n)cin>>s,mp[s]=i;
		f(i,1,m){
			cin>>s>>t;
			ll u=mp[s],v=mp[t],w=d;
			dis[u][v]=min(dis[u][v],w);
			dis[v][u]=min(dis[v][u],w);
		}f(kk,1,n)f(i,1,n)f(j,1,n)dis[i][j]=min(dis[i][j],dis[i][kk]+dis[kk][j]);
		memset(dp,0x3f,sizeof(dp));
		f(i,1,k){cin>>s;
			dp[mp[s]][1<<(i-1)]=0;
		}memset(dpp,0x3f,sizeof(dpp));
		f(msk,0,(1<<k)-1){
			f(i,1,n)for(int ms=msk;ms;ms=(ms-1)&msk)dp[i][msk]=min(dp[i][msk],dp[i][ms]+dp[i][msk^ms]);
			f(i,1,n)f(j,1,n)dp[i][msk]=min(dp[i][msk],dp[j][msk]+dis[i][j]);
		}
		f(i,0,(1<<k)-1)f(j,1,n)dpp[i]=min(dpp[i],dp[j][i]);
		f(i,0,(1<<k)-1)if(ch(i))
			for(int j=i;j;j=(j-1)&i)if(ch(j))dpp[i]=min(dpp[i],dpp[j]+dpp[i^j]);
		printf("%lld\n",dpp[(1<<k)-1]);
	}
	
	return 0;
}

[WC2008]游览计划还有Garden

都是板子,加个输出方案就行了。

posted @   jimmyywang  阅读(60)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示