Servicing Stations
题目链接:
http://acm.bnu.edu.cn/bnuoj/problem_show.php?pid=18578
题目大意:
在一个镇上有N个车站,其中有M条道路连通,现在需要在若干个车站装上服务站,而每一个服务站能够服务该站与该站直接相连的车站,问最少需要装多少服务站。
解题思路:
由于车站最多为35个。如果暴力枚举,2^35次肯定会TLE。所以这道题目的关键是如何剪枝。(DFS + 剪枝)
回溯的方法类似于二进制枚举,用一个vis数组来记录车站是否被服务站覆盖,每次有服务站建立,便将相关的车站vis[]++, 在回溯的时候,将相应的车站vis[]--。不过在枚举的过程中,要注意如下几个剪枝。
1. 如果在回溯过程中,服务站数已经大于最小值,可以减掉该分支。
2. 枚举到某个车站时,如果建立服务站的时候,并没有增加被覆盖的车站数,便没有必要建立服务站。
3. 由于是按照字典序枚举,所以在枚举的时候,如果此时的车站号已经比之前未覆盖车站相关联的最大车站号大时,说明这个未覆盖的车站肯定不能被服务到。这是便可以减掉该分支。
代码:
#include<iostream> #include<fstream> #include<cstdio> #include<cstring> #include<cmath> #include<map> #include<queue> #include<stack> #include<vector> #include<set> #include<ctype.h> #include<algorithm> #include<string> #define PI acos(-1.0) #define maxn 1000 #define INF 1<<25 #define mem(a, b) memset(a, b, sizeof(a)) typedef long long ll; using namespace std; int g[40][40], vis[40], n, mn, s[40]; bool cmp(int s, int v) { return s > v; } void dfs(int t, int sum, int cnt) { int i; if (sum >= mn) return ; // 如果sum已经大于等于mn,就不需要继续深搜 if (cnt == n) // 已经将一个分支搜索完 { if (sum < mn) mn = sum; return ; } if (t > n) return ; for (i = 1; i < t; i++) // 这个分支很重要,即之前未被服务的车站相关联的最大车站号已经小于此时遍历的车站号,说明肯定不能成功,要减掉该分支。 if (!vis[i] && g[i][0] < t) return ; int check = 0; for (i = 0; i < s[t]; i++) { if (!vis[g[t][i]]) check++; vis[g[t][i]]++; } if (check) dfs(t + 1, sum + 1, cnt + check); //只有当该车站被覆盖后,能够改变被服务的车站数,才有必要深搜 for (i = 0; i < s[t]; i++) vis[g[t][i]]--; dfs(t + 1, sum, cnt); } int main () { int m, a, b; while(scanf("%d%d", &n, &m) != EOF) { if (n + m == 0) break; mem(g, 0); mem(vis, 0); mem(s, 0); while(m--) { scanf("%d%d", &a, &b); g[a][s[a]++] = b; g[b][s[b]++] = a; } for (int i = 1; i <= n; i++) { g[i][s[i]++] = i; //车站自己与自己相关联 sort(g[i], g[i] + s[i], cmp); //把相关联的车站号按照降序排列,利于深搜 } mn = n; dfs(1, 0, 0); cout<<mn<<endl; } return 0; }