UVa 12058 - Highway Monitor

contents

  1. 1. Problem
  2. 2. Sample Input
  3. 3. Sample Output
  4. 4. Solution

Problem

在 n 個點的高速公路上,需要監視 m 條邊上的情況,監視器只能位於點上,並且可以監控所有與該點相連的邊,費用有限最多只能放置 k 個監視器,找到其中一種使用少於等於 k 個監視器的方法。

Sample Input

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
5 7 2
1 2
1 3
1 5
2 4
2 5
3 5
4 5
5 7 3
1 2
1 3
1 5
2 4
2 5
3 5
4 5

Sample Output

1
2
Case #1: no
Case #2: yes 1 2 5

Solution

解決最少點集覆蓋所有邊,是一個 NP 問題,雖然這一題已經給定限制搜索必須小於等於 K。

對於 DLX (Dancing Links and Algorithm X) 運用在最少重複覆蓋而言,K 是沒有太大的必要,事實上根據 degree 最大的節點開始窮舉的效果跟 DLX 差不多的。而這一題沒有要求最少,只是請求在範圍內的任一解輸出即可。

DLX 一開始用在精準覆蓋,解決重複覆蓋的效果只能說還算可以,大多的時間都花在計算估價函數,那個啟發式估價是用最簡單的貪心。有時候需要覆蓋的 colume 數量非常多,所以寫法上用 memset 也許是不錯的,但是用懶標記是更為快速。

DLX 的精神在於雙十字鏈表,並且每次找最少可能的 colume 開始窮舉,窮舉時會斷開所有相關在其他 colume 的可能,並在移除掉不需要窮舉的 colume,而在重複覆蓋上則不會移除掉需要窮舉的 colume。

  • 精确覆盖:
    首先选择当前要覆盖的列(含1最少的列),将该列和能够覆盖到该列的行全部去掉,再枚举添加的方法。枚举某一行r,假设它是解集中的一个,那么该行所能覆盖到的所有列都不必再搜,所以删除该行覆盖到的所有列,又由于去掉的列相当于有解,所以能够覆盖到这些列的行也不用再搜,删之。
  • 重复覆盖:
    首先选择当前要覆盖的列(同上),将该列删除,枚举覆盖到该列的所有行:对于某一行r,假设它是解集中的一个,那么该行所能覆盖到的列都不必再搜,所以删除该行覆盖到的所有列。注意此时不用删去覆盖到这些列的行,因为一列中允许有多个1。这里有一个A*的优化:估价函数h意义为从当前状态最好情况下需要添加几条边才能覆盖。

這一題會有 n 個 row,每一個 row 會覆蓋第 i 個節點覆蓋哪些邊。因此需要對所有輸入的邊進行編號,也就是 colume 會有 m 個。m 最大 50 萬,窮舉起來相當刺激。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <vector>
#include <algorithm>
#include <assert.h>
using namespace std;
#define MAXV 0x3f3f3f3f
#define MAXE 1048576
#define MAXC 1048576
#define MAXR 1024
struct DacingLinks {
int left, right;
int up, down;
int ch, rh;
int data; // extra info
} DL[MAXE];
int s[MAXC], o[MAXR], head, size, Ans, findflag;
void Remove(int c) {
static int i;
for(i = DL[c].down; i != c; i = DL[i].down) {
DL[DL[i].right].left = DL[i].left;
DL[DL[i].left].right = DL[i].right;
s[DL[i].ch]--;
}
}
void Resume(int c) {
static int i;
for(i = DL[c].down; i != c; i = DL[i].down) {
DL[DL[i].right].left = i;
DL[DL[i].left].right = i;
s[DL[i].ch]++;
}
}
int used[MAXC] = {};
int H() {
static int c, ret, i, j, time = 0;
for(c = DL[head].right, ++time, ret = 0; c != head; c = DL[c].right) {
if(used[c] != time) {
ret ++, used[c] = time;
for(i = DL[c].down; i != c; i = DL[i].down)
for(j = DL[i].right; j != i; j = DL[j].right)
used[DL[j].ch] = time;
}
}
return ret;
}
void DFS(int k) {
if(k + H() >= Ans) return;
if(DL[head].right == head) {
if(k < Ans) {
Ans = k, findflag = 1;
printf("yes");
for (int i = 0; i < k; i++)
printf(" %d", DL[o[i]].data);
puts("");
}
return;
}
int t = MAXV, c, i, j;
for(i = DL[head].right; i != head; i = DL[i].right) {
if(s[i] < t) {
t = s[i], c = i;
}
}
for(i = DL[c].down; i != c; i = DL[i].down) {
o[k] = i;
Remove(i);
for(j = DL[i].right; j != i; j = DL[j].right) Remove(j);
DFS(k+1);
for(j = DL[i].left; j != i; j = DL[j].left) Resume(j);
Resume(i);
if (findflag) break;
}
}
int new_node(int up, int down, int left, int right) {
assert(size < MAXE);
DL[size].up = up, DL[size].down = down;
DL[size].left = left, DL[size].right = right;
DL[up].down = DL[down].up = DL[left].right = DL[right].left = size;
return size++;
}
void addrow(int n, int Row[], int data) {
int a, r, row = -1, k;
for(a = 0; a < n; a++) {
r = Row[a];
DL[size].ch = r, s[r]++;
DL[size].data = data;
if(row == -1) {
row = new_node(DL[DL[r].ch].up, DL[r].ch, size, size);
DL[row].rh = a;
}else {
k = new_node(DL[DL[r].ch].up, DL[r].ch, DL[row].left, row);
DL[k].rh = a;
}
}
}
void init(int m) {
size = 0;
head = new_node(0, 0, 0, 0);
int i;
for(i = 1; i <= m; i++) {
new_node(i, i, DL[head].left, head);
DL[i].ch = i, s[i] = 0;
}
}
int main() {
int testcase, cases = 0;
int n, m, k, x, y;
scanf("%d", &testcase);
while (testcase--) {
scanf("%d %d %d", &n, &m, &k);
vector<int> g[1024];
for (int i = 1; i <= m; i++) {
scanf("%d %d", &x, &y);
g[x].push_back(i);
g[y].push_back(i);
}
assert(m < MAXC);
init(m);
int row[1024];
for (int i = 1; i <= n; i++) {
sort(g[i].begin(), g[i].end());
for (int j = 0; j < g[i].size(); j++)
row[j] = g[i][j];
addrow(g[i].size(), row, i);
}
printf("Case #%d: ", ++cases);
Ans = k + 1, findflag = 0;
DFS(0);
if (Ans == k+1)
puts("no");
}
return 0;
}