算法提高 Cutting Chains (dfs + 状态压缩)

试题 算法提高 Cutting Chains

资源限制
时间限制:1.0s 内存限制:256.0MB
问题描述
  什么!Anna Locke最近买了几个链环,并且其中的一些链环连接到了一起。它们是由zorkium做成的,这是一种在上世纪经常用来加工珠宝的材料,但它不再用来干这种事了。它有它独特的光泽,即使黄金和白银也无法与之比较,并且无法向任何以前没有见过它的人来描述它。
  Anna想要把这些链环拼组成连续的一段链,这个段中的环节首尾相连。她把这些链环带到一个珠宝商那里,珠宝商告诉她合并这些链环的费用取决于必须打开和关上的环的数目。为了最小化这个费用,她小心地计算为了合并成一个单独的序列所需要打开的最小的环的数目。这个比她想象中更困难。你必须为她解决这个问题。
输入格式
  输入包含多组关于链环集合的描述,对于每一组。每个链环集合由一行用一个或多个空格隔开的数字表示。每个描述由一个n开始,表示链环集合中环的数量。我们把这些环编号为1,2,…,n。紧接着n后面的整数描述了哪些环连接在一起。每个连接由一对整数i,j(
  1<=i,j<=n并且i≠j)来描述,代表环i和环j连接,即一个穿过另一个。每个链环集合用一对-1 -1表示结束(-1 -1 不用进行计算)
  输入用n=0表示结束,并且n=0不用进行计算
输出格式
  对于每一个输入的链环集合,输出一行形如
  Set N: Minimum links to open is M
  N的环的个数,M是最小需要打开然后关闭的次数来使所有环组成一条单独的链。
  1<=i,j<=n并且i≠j
样例输入
5 1 2 2 3 4 5 -1 -1
7 1 2 2 3 3 1 4 5 5 6 6 7 7 4 -1 -1
4 1 2 1 3 1 4 -1 -1
3 1 2 2 3 3 1 -1 -1
3 1 2 2 1 -1 -1
0
样例输出
Set 1: Minimum links to open is 1
Set 2: Minimum links to open is 2
Set 3: Minimum links to open is 1
Set 4: Minimum links to open is 1
Set 5: Minimum links to open is 1
数据规模和约定
  1<=n<=15**

解题思路

可以把环看成结点,两个环如果相扣,则两个点是直接连通的,两个结点间添上一条边,这样就可以这样想,原来的初始状态是一个图,目标状态是 只有头尾两个结点的度数为1,其余节点度数为2的图。
而原始的图状态很多很乱很杂,可以是两条链(样例第一行),这样只需要打开一条链的两端的环,和另外一条链的一端相接;也可能是一条链环(样例第四行),这样只需要随便取一个环断开即可;也可能是更加混乱的状况(画图)…而此时我们注意到数据规模给的很小,n<=15,意味着我们可以暴力求解(不用考虑太多的细节,找到一个能得到答案的傻瓜方法就行,个人感觉,不暴力想要得出答案要考虑的太多了…

首先明确目标:

  • 图中不可能存在度为3或者以上的结点
  • 图中不可能存在环
  • 图肯定是连通的

我们打开一个环,可以暂时把它拿出来,不去管它,因为如果除了它,其余结点的图满足上述要求,则把这个环放在末尾即可;如果拿掉打开的环以后,图分为了两个图,如果这两个图都满足上述要求,那么可以用这个环把这两个连通分量连接为一个(连通分量个数 <= 打开的环数-1)。

好,我们更新一下目标,允许目标图不连通:

  • 图中不可能存在度为3或者以上的结点(遍历图)
  • 图中不可能存在环(dfs 搜索)
  • 连通分量个数 <=打开的环数-1(dfs 搜索)
    然后就可以暴力求解了,利用状态压缩,开一个 int 的变量 s,转换成二进制,二进制的每一位就代表每一个环,如果打开这个环,那么相应位置上就是1,否则为0。在判断某个环 i 有没有拿走时(下标为0),这样写
	if( s & (1<<i) )

完整代码如下:(不难理解)

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;

int n, cnt, number;
int map[20][20];		//可以用邻接矩阵存储,因为最多15个顶点
int vis[20];
int ans;

bool two(int s)  //判断是否有度数数大于2的圆环
{
	for (int i = 0; i < n; i++)
	{
		int cnt = 0;  //记录度数
		for (int j = 0; j < n; j++)
		{
			//如果圆环i和j连通并且没有打开i或j时,i圆环的分支数+1
			if (map[i][j] && !(s&(1 << i)) && !(s & 1 << j))
			{
				cnt++;
				if (cnt == 3)  return true;
			}
		}
	}
	return false;
}

bool dfs(int x, int f, int s)   //判断是否有回路存在
{
	vis[x] = 1;
	for (int i = 0; i < n; i++)
	{
		if (map[x][i])
		{
			if (i == f || (s&(1 << i))) continue;  //如果i是上一次访问的圆环或者i圆环被打开,进行下一次判定
			if (vis[i])     return true;  //存在回路
			if (dfs(i, x, s)) return true;
		}
	}
	return false;
}

bool circle(int s)		//判断是否有环出现,同时记录连通分量数
{
	memset(vis, 0, sizeof(vis));
	for (int i = 0; i < n; i++)
	{
		if (!vis[i] && !(s & (1 << i)))
		{
			number++;   	//连通分量数+1
			if (dfs(i, -1, s)) return true;
		}
	}
	return false;
}

int calc(int s)  //计算出打开圆环的个数
{
	int cnt = 0;
	for (int j = 0; j < n; j++)
	{
		if (s&(1 << j))   cnt++;	//位运算
	}
	return cnt;
}

void solve()
{
	ans = 100000;
	for (int i = 0; i < (1 << n); i++)  //二进制枚举打开圆环的情况
	{
		number = 0;
		if (two(i) || circle(i))  continue;  //如果不行,进行下一次判断,如果不存在两个分支或回路,则正好计算出了连通分支数
		int count = calc(i);
		if (number - 1 <= count)   //连通分支数-1<=打开的圆环数
			ans = min(ans, count); 
	}
}

int main()
{
	//freopen("D:\\txt.txt", "r", stdin);
	int a, b, kase = 0;
	while (cin >> n && n)
	{
		memset(map, 0, sizeof(map));
		while (cin >> a >> b && a != -1 && b != -1)
		{
			map[a - 1][b - 1] = 1;
			map[b - 1][a - 1] = 1;
		}
		solve();
		cout << "Set " << ++kase << ": Minimum links to open is " << ans << endl;
	}
	return 0;
}

代码来源这里:https://www.cnblogs.com/zyb993963526/p/6350198.html

发布了17 篇原创文章 · 获赞 2 · 访问量 432

猜你喜欢

转载自blog.csdn.net/Raymond_YP/article/details/104381477