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

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

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

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

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

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

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

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

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

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

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

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

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

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

显然有 \(g((1)_2)=\min f(i,(1001)_2)=w_1+w_2+w_3\)\(g((10)_2)=\min f(i,(0110)_2)=w_2\)

那么会得出来:\(g((11)_2)=w_1+2w_2+w_3\)

但显然 \(g((11)_2)\) 应该等于 \(w_1+w_2+w_3\)

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

如何解决?

我想到的解决办法:

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

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

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

最后再按原来的方法,更新:\(g(sta')=\min(g(s)+g(sta'-s))\)。(其中 \(s\)\(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\n",g[anssta]);
	return 0;
}
posted @ 2022-10-28 19:40  ez_lcw  阅读(22)  评论(0)    收藏  举报