【BZOJ4006】【JLOI2015】管道连接(斯坦纳树)

看到题面容易想到跟斯坦纳树有关。

那么我们不妨设 f ( i , s t a ) f(i,sta) f(i,sta) 表示根为 i i i,关键点的状压状态为 s t a sta sta 时的最小代价。

那么所有的 f ( i , s t a ) f(i,sta) f(i,sta) 我们都可以用斯坦纳树的模板求出来。

现在考虑如何达到题目的要求。

因为考虑到频道的数量也小于 10 10 10,所以考虑一下能不能也用状压解决。

g ( s t a ′ ) g(sta') g(sta) 表示频道的状压状态为 s t a ′ sta' sta 时的最小代价。(如果 s t a ′ sta' sta 二进制下的第 k k k 位是 1 1 1 就说明第 k k k 个频道的关键点都已经联通了,否则不连通)。

然后设 s t a p ( i ) stap(i) stap(i) 表示第 i i i 个频道的所有关键点的状压状态(类似于 f ( i , s t a ) f(i,sta) f(i,sta) 中的 s t a sta sta)。

显然,这个东西可以在读入的时候预处理。

然后说一下我一开始的想法:

对于每一个频道 i i i,把 g ( 1 < < i ) g(1<<i) g(1<<i) 的值设为 min ⁡ ( f ( j , s t a p i ) ) min(f(j,stap_i)) min(f(j,stapi))。然后其他的 g ( ) g() g() 都设为 inf ⁡ inf inf

然后枚举每一个 s t a ′ sta' sta 的状态,更新: g ( s t a ′ ) = min ⁡ ( g ( s ) + g ( s t a ′ − s ) ) g(sta')=min(g(s)+g(sta'-s)) g(sta)=min(g(s)+g(stas))。(其中 s s s s t a ′ sta' sta 的子集)

但是我发现了一个重要的问题:

比如说这个图中,所有节点都是关键节点,点 1 1 1 4 4 4 是频道 1 1 1 的关键节点,点 2 2 2 3 3 3 是频道 2 2 2 的关键节点。(点上的黑色数字代表点的编号,红色数字代表这个点所属的频道, 边上的数字代表这条边的权值)。

显然有 g ( ( 1 ) 2 ) = min ⁡ f ( i , ( 1001 ) 2 ) = w 1 + w 2 + w 3 g((1)_2)=min f(i,(1001)_2)=w_1+w_2+w_3 g((1)2)=minf(i,(1001)2)=w1+w2+w3 g ( ( 10 ) 2 ) = min ⁡ f ( i , ( 0110 ) 2 ) = w 2 g((10)_2)=min f(i,(0110)_2)=w_2 g((10)2)=minf(i,(0110)2)=w2

那么会得出来: g ( ( 11 ) 2 ) = w 1 + 2 w 2 + w 3 g((11)_2)=w_1+2w_2+w_3 g((11)2)=w1+2w2+w3

但显然 g ( ( 11 ) 2 ) g((11)_2) g((11)2) 应该等于 w 1 + w 2 + w 3 w_1+w_2+w_3 w1+w2+w3

这时候就会发现这样 dp 会算重边。

如何解决?

我想到的解决办法:

我们一开始初始化 g ( ) g() g() 的时候,本来是只初始化所有的 g ( 1 < < i ) g(1<<i) g(1<<i),而我们现在把 g ( ) g() g() 的每一个状态都初始化。

意思是说,对于每一个 s t a ′ sta' sta,我们都用 f ( ) f() f() g ( s t a ′ ) g(sta') g(sta) 进行初始化。

具体过程可以看代码,用 dfs 实现。

最后再按原来的方法,更新: g ( s t a ′ ) = min ⁡ ( g ( s ) + g ( s t a ′ − s ) ) g(sta')=min(g(s)+g(sta'-s)) g(sta)=min(g(s)+g(stas))。(其中 s s s s t a ′ sta' sta 的子集)

代码如下:

#include<bits/stdc++.h>

#define N 1010
#define M 3010

using namespace std;

int n,m,p,anssta,id[15],num[15],stap[15];
int cnt,head[N],w[M<<1],to[M<<1],nxt[M<<1];
int f[N][1025],g[1025];
bool inq[N];

//anssta是答案的状压状态(形如sta')
//stap是每一个频道对应的状压状态(形如sta)

queue<int>q;

void adde(int u,int v,int wi)
{
	to[++cnt]=v;
	w[cnt]=wi;
	nxt[cnt]=head[u];
	head[u]=cnt;
}

void spfa(int sta)
{
	while(!q.empty())
	{
		int u=q.front();
		q.pop();
		inq[u]=false;
		for(int i=head[u];i;i=nxt[i])
		{
			int v=to[i];
			if(f[u][sta]+w[i]<f[v][sta])
			{
				f[v][sta]=f[u][sta]+w[i];
				if(!inq[v])
				{
					inq[v]=true;
					q.push(v);
				}
			}
		}
	}
}

void dfs(int k,int sum,int sump)
{
	if(k==p+1)
	{
		for(int i=1;i<=n;i++)
			g[sump]=min(g[sump],f[i][sum]);
		return;
	}
	dfs(k+1,sum|stap[k],sump|(1<<(k-1)));//枚举选这个频道(sta'的第k位是1)
	dfs(k+1,sum,sump);//不选这个频道(sta'的第k位是0)
}

int main()
{
	memset(f,0x3f,sizeof(f));
	memset(g,0x3f,sizeof(g));
	scanf("%d%d%d",&n,&m,&p);
	for(int i=1;i<=m;i++)
	{
		int u,v,w;
		scanf("%d%d%d",&u,&v,&w);
		adde(u,v,w),adde(v,u,w);
	}
	for(int i=1;i<=p;i++)
	{
		scanf("%d%d",&id[i],&num[i]);
		f[num[i]][1<<(i-1)]=0;
		stap[id[i]]|=(1<<(i-1));
		anssta|=(1<<(id[i]-1));
	}
	int maxn=(1<<p)-1;
	for(int sta=1;sta<=maxn;sta++)
	{
		for(int i=1;i<=n;i++)
		{
			for(int now=sta;now;now=sta&(now-1))
				f[i][sta]=min(f[i][sta],f[i][now]+f[i][sta-now]);
			if(f[i][sta]!=0x3f3f3f3f)
			{
				q.push(i);
				inq[i]=true;
			}
		}
		spfa(sta);
	}
   //以上是模板斯坦纳树
	dfs(1,0,0);
	for(int sta=1;sta<=anssta;sta++)
		for(int now=sta;now;now=sta&(now-1))
			g[sta]=min(g[sta],g[now]+g[sta-now]);
	printf("%d
",g[anssta]);
	return 0;
}
原文地址:https://www.cnblogs.com/ez-lcw/p/14448672.html