题意:几个城镇要建立station,输入了n个城镇,和一些城镇的连结情况,要求每个城镇自己建一个station或者和自己相连的城镇有station,输出建立的最少statiion数量。
题解:此题有两种做法,dfs + 剪枝, 状态压缩 + 剪枝。
第一种:只递归会超时,有三个需要剪枝的地方:1.判断如果当前已经建立的station超过了最小值就return;2.判断如果在当前结点建立了station后,并没有增加station的覆盖度,就不再建立;3.如果当前结点是 i ,小于 i 的结点如果没有被覆盖且它的最大连结点小于i,就return;
代码如下:
#include <string.h>
#include <stdio.h>
#include <algorithm>
using namespace std;
const int N = 40;
int g[N][N], n, m, ans, vis[N], maxx[N];
void init() {
for (int i = 1; i <= n; i++) {
vis[i] = 0;
maxx[i] = 0;
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++)
g[i][j] = 0;
g[i][i] = 1; //城镇自身覆盖
}
ans = n;
}
void dfs(int num, int count, int k) { //num已经覆盖结点个数 , count建立station的个数, k是当前结点
if (count >= ans) //剪枝1
return;
if (num == n)
ans = count;
for (int i = 1; i < k; i++) //剪枝3
if (!vis[i] && maxx[i] < k)
return;
int temp[N], a = 0;
dfs(num, count, k + 1);
for (int i = 1; i <= n; i++)
if (g[k][i] && !vis[i]) {
vis[i] = 1;
temp[a++] = i;
}
if (a == 0) //剪枝2
return;
dfs(num + a, count + 1, k + 1); //从下一个结点判断
for (int i = 0; i < a; i++) //回溯
vis[temp[i]] = 0;
}
int main() {
int a, b;
while (scanf("%d%d", &n, &m) && (m || n)) {
init();
for (int i = 0; i < m; i++) {
scanf("%d%d", &a, &b);
g[a][b] = g[b][a] = 1;
}
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
if (g[i][j] && maxx[i] < j)
maxx[i] = j; //找出每个结点最大连结点
dfs(0, 0, 1);
printf("%d\n", ans);
}
return 0;
}
第二种:状态压缩是get新技能,学别人的,用二进制表示覆盖状态,1为覆盖,0为未覆盖,然后递归剪枝。
#include <stdio.h>
#include <string.h>
#define ll long long
const int N = 40;
int n, m;
ll vis[N], state[N];
bool dfs(ll st, int now, int tar, int cur) {
if (now == tar)
if (st == ((ll)1 << n) - 1)
return true;
else
return false;
for (int i = cur; i <= n; i++) {
if ((st | vis[i]) != ((ll)1 << n) - 1) //如果把当前结点之后的结点都覆盖也不能达到全覆盖就return
return false;
if ((st | state[i]) == st) //如果在当前结点建立station后每个结点的状态不变,就不用建立了
continue;
if (dfs((st | state[i]), now + 1, tar, cur + 1))
return true;
}
return false;
}
int main() {
int a, b;
while (scanf("%d%d", &n, &m) && (m || n)) {
for (int i = 0; i < N; i++)
vis[i] = 0;
for (int i = 0; i < n; i++) //保证能连结到自身
state[i + 1] = ((ll)1 << i);
while (m--) {
scanf("%d%d", &a, &b);
state[a] |= ((ll)1 << (b - 1));
state[b] |= ((ll)1 << (a - 1));
}
vis[n] = state[n];
for (int i = n - 1; i > 0; i--)
vis[i] = vis[i + 1] | state[i]; //vis数组保存了从i到n的总覆盖度,供剪枝使用
for (int i = 1; i <= n; i++)
if (dfs((ll)0, 0, i, 1)) {
printf("%d\n", i);
break;
}
}
return 0;
}