题目描述
古往今来的各种传说中存在着很多魔法阵,它们的激活方式也各不相同。传说在北师大电子楼前的小花园里就有一个魔法阵,上次出现还是在很多很多很多年前,但是现在它又出现了!
这时,小Q同学面无表情地说:“传说这个魔法阵都会在都会在来自远古的恶魔Awesome Crystal Monster(ACM)降临的时候出现,现在,这个时候终于要到来了吗!”话音未落,小Q同学已经走到了魔法阵前,取出一个小瓶,开始用瓶中的圣水激活这个魔法阵,“你们快退后,这里就交给我了!”
这个魔法阵由一些点和一些连接这些点的边构成,当小Q同学把圣水滴在一个点上时,和这个点相连的所有边就会被点亮并发出神圣的光芒,当魔法阵所有的边都点亮的时候,就会出现强大的神圣力量。但是小Q同学拥有的圣水非常有限,只有10滴,现在小Q同学想知道他最少可以用多少滴圣水点亮整个魔法阵,如果耗尽圣水也不能点亮,就只能打出一波GG了。
输入描述:
第一行包含一个正整数T(≤ 20),表示测试数据的组数,
对于每组测试数据,
第一行是两个整数n(1 ≤ n ≤ 1000)和m(1 ≤ m ≤ 2000),表示魔法阵的点数和边数,
接下来m行,每行包含两个整数u,v(1 ≤ u,v ≤ n),表示有一条边连接了u号点和v号点。
输出描述:
对于每组测试数据,如果使用不超过10滴圣水可以点亮整个魔法阵,输出最少需要的圣水滴数,否则输出"GG"(不含引号)。
示例1
输入
2
4 3
1 2
1 3
1 4
4 4
1 2
2 3
3 4
4 1
输出
1
2
说明
对于第一组样例,最优方案是在1号点滴一滴圣水,
对于第二组样例,一个最优方案是在1号点和3号点各滴一滴圣水。
此题可以用爆搜也可用贪心,下面我将会附上两种方法的解释及代码,都是参考大佬的思路
方法一:爆搜
思路来源:http://www.cnblogs.com/GorgeousBankarian/p/10384302.html
思路:此方法巧妙的一点就是一条边上连接的两个点如果有任何一个点被点亮过那么这条边也会被点亮,则下一次遇到这条边时就无需使用圣水,如果两个点都没有被点亮那么就可以对两个点分别进行爆搜找到m条边都被点亮时使用圣水的最小值即可,若不能全部点亮则输出“GG“,详细步骤请看注释
#include <iostream>
#include <cstring>
using namespace std;
const int N = 2005;
const int Inf = 0x7fffffff;
bool vislen[N];/*判断是否点亮这条边*/
struct Node
{
int u,v;//每条边的两个端点
}node[N];/*存入边的端点*/
int n,m,ans;
void dfs(int sum/*点亮的边的条数*/,int cnt/*使用的圣水的数量*/)
{
if(cnt > 10)/*如果使用的圣水超过10滴则return*/
{
return;
}
if(sum == m)/*如果所有边都点亮则记录下使用圣水的数量*/
{
ans = min(ans,cnt);//取每次点亮所有边时使用圣水的最小次数
return ;
}
if(vislen[node[sum].v] || vislen[node[sum].u] )/*如果这个边连的任意一个点点亮过则这条边已经被点亮无需使用圣水点亮*/
{
dfs(sum+1,cnt);//进入下一条边
}
else/*以上情况都不满足则开始使用圣水点亮这条边的点*/
{
vislen[node[sum].v] = 1;
dfs(sum+1,cnt+1);
vislen[node[sum].v] = 0;
vislen[node[sum].u] = 1;
dfs(sum+1,cnt+1);
vislen[node[sum].u] = 0;
}
}
int main()
{
int t;
cin >> t;
while(t--)
{
memset(vislen,0,sizeof(vispoint));//初始化判重数组
ans = Inf;//每次都把ans初始化为最大值
cin >> n >> m;
for(int i = 0; i < m ; i++)
{
cin >> node[i].u >> node[i].v;//输入每条边的端点
}
dfs(0,0);//进行爆搜
if(ans <= 10) cout << ans << endl;//每条边都点亮
else cout << "GG" << endl;//不能点亮每条边
}
return 0;
}
方法二:贪心
思路来源:http://www.cnblogs.com/kirai/p/6744244.html
思路:使用vector数组将边数存入对应点,每次都找出连接边数最多的点并点亮其连接的边即用再去掉这个点即用visited标记这个点连接的所有边,直到找不到这样的点,最后输出所用圣水的数量即可,若没有点亮所有的边则输出GG
详见注释
#include <iostream>
#include <vector>
#include <cstring>
using namespace std;
const int N = 2005;
bool visited[N];
int n,m;
int main()
{
int t;
cin >> t;
while(t--)
{
vector<int>a[N];
memset(visited,false,sizeof(visited));
cin >> n >> m;
for(int i = 0 ; i < m ; i++)/*边数*/
{
int u,v;
cin >> u >> v;
a[u].push_back(i);/*将边数存入对应的点统计每个点所连接的总边数*/
a[v].push_back(i);
}
int pos;
bool flag = false;
for(int i = 0 ; i <= 10; i++)
{
int num = 0;/*记录连接最多边的点*/
for(int j = 1 ; j <= n; j++)/*遍历每个店*/
{
int temp = 0;/*统计j这个点连接的所有边*/
for(int k = 0; k < a[j].size(); k++)
{
if(!visited[a[j][k]])/*如果这个点连接的这条边没被点亮过*/
{
temp++;
}
}
if(temp > num)/*找到了一个连接最多边的点*/
{
num = temp;
pos = j;/*记下这个点*/
}
}
if(num == 0)/*如果没有连接的边数最多的点即所有边都被点亮则输出圣水使用的数量*/
{
cout << i << endl;
flag = true;
break;
}
for(int g = 0 ; g < a[pos].size(); g++)
{
visited[a[pos][g]] = 1;/*将记下的点亮的点所连接的所有边都点亮*/
}
}
if(!flag)//如果没有所有的边都点亮
{
cout << "GG" <<endl;
}
}
return 0;
}