【图论·习题】最小生成树:Buy or Build

Problem

World Wide Networks (WWN) is a leading company that operates large telecommunication networks.
WWN would like to setup a new network in Borduria, a nice country that recently managed to get rid
of its military dictator Kurvi-Tasch and which is now seeking for investments of international companies
(for a complete description of Borduria, have a look to the following Tintin albums \King Ottokar’s
Sceptre", \The Calculus Affair" and \Tintin and the Picaros"). You are requested to help WWN
todecide how to setup its network for a minimal total cost.
There are several local companies running small networks (called subnetworks in the following) that
partially cover the n largest cities of Borduria. WWN would like to setup a network that connects all
n cities. To achieve this, it can either build edges between cities from scratch or it can buy one or
several subnetworks from local companies. You are requested to help WWN to decide how to setup its
network for a minimal total cost.
• All n cities are located by their two-dimensional Cartesian coordinates.
• There are q existing subnetworks. If q  1 then each subnetwork c (1  c  q) is de ned by a set
of interconnected cities (the exact shape of a subnetwork is not relevant to our problem).
• A subnetwork c can be bought for a total cost wc and it cannot be split (i.e., the network cannot
be fractioned).
• To connect two cities that are not connected through the subnetworks bought, WWN has to build
an edge whose cost is exactly the square of the Euclidean distance between the cities.
You have to decide which existing networks you buy and which edges you setup so that the total
cost is minimal. Note that the number of existing networks is always very small (typically smaller than
8).

中文翻译:
平面上有n个点,你的任务是让所有n个点连通,为此,你可以新建
一些边,费用等于两个端点的欧几里得距离的平方。另外还有q个套餐,
可以购买,如果你购买了第i个套餐,该套餐中的所有结点将变得相互
连通,第i个套餐的花费为ci。求最小花费。
1 ≤ n ≤ 1000 ; 0 ≤ q ≤ 8 。 1≤n≤1000; 0≤q≤8。 1n1000;0q8

Solution

首先补充一个知识点,任意两点(a,b),(c,d)的欧几里得距离是:
d i s = ( a − c ) 2 + ( b − d ) 2 dis=(a-c)^2+(b-d)^2 dis=(ac)2+(bd)2

相信要是不购买套餐,题目就非常简单:直接将这些点两两相连,边权为欧几里得距离,跑一遍最小生成树即可。

如果购买套餐,我们不确定具体需要购买哪些套餐。此时我们观察数据,q的取值范围很小,直接朴素的枚举每一个选择情是不会超时的。复杂度 O ( 2 q ) O(2^q) O2q

在每一次的枚举中,我们需要做如下操作:

  • 累计套餐的话费
  • 将套餐的所有点合并为一个并查集。选用并查集的原因是因为我们使用Kruscal来做MST;合并为一个并查集的作用是相当于使这些点之间已经有边相连,使它们互相联通。

然后操作以后,就是最小生成树MST的事情了。

需要注意的是,枚举每一种情况需要用二进制状态压缩取枚举而不要dfs,否则有超时的风险。

#include
#include
#include
#include
#include
#include
#include
#include

#define Sc(a) scanf("%d",&a) 
#define Sc2(a,b) scanf("%d%d",&a,&b)
#define Pr(a) printf("%d\n",a);
#define Mp make_pair
#define Mem(a,b) memset(a,b,sizeof(a))
#define dis(a,b) ((x[a]-x[b])*(x[a]-x[b])+(y[a]-y[b])*(y[a]-y[b]))
#define father for(int i=0;i<=n;++i)fa[i]=i

using namespace std;

int n,q,h;
int temp;
int tot=0;
int Count=0;
int x[3000];
int y[3000];
int fa[3000];
int cost[3000];

struct edge
{
	int u;
	int v;
	int val;
};
edge a[2000 * 2000];

vector<int>c[3000];

int get(int x)
{
	if (fa[x]==x) return x;
	return fa[x]=get(fa[x]);
}

int Kruscal(void)
{
	int sum=0,cnt=0;
    for (int i=1;i<=tot;++i)
    {
    	int fu=get(a[i].u);
    	int fv=get(a[i].v);
    	int val=a[i].val;
    	if (fu==fv) continue;
    	sum+=val;
    	fa[fu]=fv;
    	if (++cnt==n-1) break;
	}
	return sum;
}
//计算最短路 

inline bool cmp(edge p1,edge p2) {
	return p1.val<p2.val;
}

void work(void)
{
	Mem(x,0);
	Mem(y,0);
	Mem(cost,0);
	Sc2(n,q);
	for (int i=0;i<3000;++i)
	    c[i].clear();
	for (int i=0;i<q;++i)
	{
		int m;
		Sc2(m,cost[i]);
		for (int j=1;j<=m;++j) 
		{
			Sc(temp);
			c[i].push_back(temp);
		}
	}
	for (int i=1;i<=n;++i) Sc2(x[i],y[i]);
	//input
	
	tot=0;
	for (int i=1;i<=n;++i)
	    for (int j=i+1;j<=n;++j)
		    a[++tot]=edge{i,j,dis(i,j)}; 
	sort(a+1,a+tot+1,cmp);
	
	father;
	int ans=Kruscal();
	for (int i=0;i<(1<<q);++i)
	//枚举套餐的选择方案 
	{
		father;
		int spend=0;
		for (int j=0;j<q;++j)
		//枚举二进制位数 
		{
			if (((i>>j)&1)==0) continue;
			//第j位为0则不选择套餐
			spend+=cost[j];
			//累计费用 
			for (int k=1;k<c[j].size();++k) 
			{
				int point1=get(c[j][0]);
				int point2=get(c[j][k]);
				fa[point1]=point2;
			}
			//将同一个套餐内的点加进一个集合&缩点 
		}
		ans=min(ans,spend+Kruscal());
	}
	
	printf("%d",ans);
}
int main(void)
{
	int T;
	h=T;
	Sc(T);
	while (T--) 
	{
	    work();
	    if (T) printf("\n\n");
	    else printf("\n");
	}
	return 0;
}

主要思路就是:观察数据,枚举,生成树计算。

但这道题最主要的难点就是代码量大。

你可能感兴趣的:(图论)