一、監控相機佈局問題
1、問題背景
爲保障師生的生命財產安全,學校計劃在校園內安裝視頻監控,確保每一個 角落都有相機覆蓋。爲簡化問題,可以考慮將三維空間離散化成一系列採樣點 (Sample),而監控相機只能安裝在有限的候選機位(Candidate)上,受限於鏡 頭分辨率和遮擋物,安裝在每一個候選機位上的相機只能覆蓋數量有限的採樣點。 監控相機佈局問題要求在每個採樣點都有相機覆蓋的情況下,最小化使用的相機 數量。
2、形式化描述
進行離散化採樣之後,相機佈局問題可以抽象爲經典的集合覆蓋問題。每個 採樣點是一個元素(Element),每個候選機位是一個集合(Set)。
已知所有元素構成的集合爲𝐸 = {𝑒1,𝑒2,…,𝑒𝑛},所有集合的集合爲𝑆 = {𝑠1,𝑠2,…,𝑠𝑚},其中每個集合𝑠𝑖 ⊂𝐸。集合覆蓋問題要求選出一個子集𝑋⊂𝑆,使 得選出的集合的數量儘可能少(最小化|𝑋|),同時使得子集中所有集合的並集包 含所有元素(⋃𝑠𝑖∈𝑋 𝑠𝑖 = 𝐸)。
集合覆蓋問題的實例可以用二分圖表示。以如下二分圖爲例,字母 A、B、 C 爲集合(候選機位),數字 1、2、3、4、5 爲元素(採樣點)。無向邊表示覆蓋 關係,例如邊 A-1 表示集合 A 可以覆蓋元素 1。
顯然,上圖所示的實例的最優解爲𝑋 = {𝐴, 𝐶},此時總共選出了兩個集合, 且所有元素均至少被一個集合覆蓋。𝑋 = {𝐴, 𝐵}爲不可行解,因爲元素 5 既不能 被 A 覆蓋也不能被 B 覆蓋。𝑋 = {𝐴, 𝐵, 𝐶}是一個可行解,但因爲總共選出了三個 集合,選中集合數量較多,故不是最優解。
3、輸入輸出格式
3.1、輸入數據格式
每個算例文件格式如下。
第一行有兩個數字,依次爲元素數 n 和集合數 m。
接下來有 3*n 行,其中每 3 行一組,第一行爲元素編號 e,第二行爲能覆蓋
元素 e 的集合數,第三行爲能覆蓋元素 e 的集合編號列表。 下面給出了一個具體的算例(注意實際算例中沒有註釋)。
3.2、輸出結果格式
第一行輸出選中的集合數,第二行輸出選中的集合編號列表。下面給出了一個具體的解文件(注意輸出的解文件不要包含註釋)。
4、重要提示
集合覆蓋問題爲 NP 問題,與 ACM/ICPC、IOI 等競賽中的 P 問題不同,該 類問題往往難以在有限的時間內求得最優解。因此,建議給你的程序設置足夠長 的運行時間限制(建議不少於 5 分鐘),超時之後保存目前找到的最好結果。
推薦使用的算法(僅供參考):貪心構造、局部搜索、遺傳算法、蟻羣算法、 分支限界、強化學習等。
二、解決方法
貪心算法
核心策略:優先選擇監測點最多的攝像頭,監測點的數量是動態的
具體解釋
1、先按照攝像頭監控監測點數量由大到小排序(每個攝像頭之間都有交集,即某個監測點有可能被多個攝像頭監控)
2、把監控監控點數量最大的攝像頭取出
3、標記該攝像頭監控的監控點
4、把剩餘的攝像頭監控的監控點中被標記的監控點移除
5、回到第一步
三、具體實現(Java)
Camera.java(攝像頭實體)
public class Camera {
// 攝像頭編號
int number;
// 該攝像頭邏輯監控地區個數
int sum;
public Camera(int number, int sum) {
this.number = number;
this.sum = sum;
}
}
Coverage.java(核心方法)
import java.util.*;
/**
* 貪心算法
* 核心策略:優先選擇監測點最多的攝像頭,監測點的數量是動態的
*
* 設有n個監測點,編號由0~n-1
* 設有m個攝像頭,編號又0~m-1
* 1、移除沒有監控監測點的攝像頭
* 2、對m個攝像頭按照該攝像頭監控監測點的數量由大到小排序(貪心策略)
* 3、獲取監控監測點數量最多的攝像頭p
* 4、獲取p攝像頭監控的監測點集合f
* 5、對集合進行遍歷,同時在n數組中,標記這些監測點(需要而外創建一個n長度的bool型的數組)
* 6、查找攝像頭集合,依次刪除某攝像頭中存在於f集合的監測點(這裏利用邏輯刪除,效率不會太低)
* 接着到第2步
*/
public class Coverage {
// 讀取文件的路徑
static final String TXT_PATH = "";
public void coverageProblem() {
List<String> list = ControllerFile.readTxt(TXT_PATH);
if(list == null) {
return;
}
String[] ss = list.get(0).split(" ");
int n = Integer.parseInt(ss[0]);
int m = Integer.parseInt(ss[1]);
// 地區集
int[] regions = new int[n];
// 每個攝像頭對應覆蓋地區的集合
Map<Integer, List<Integer>> cameraRegionsMap= new HashMap<>();
// 每個攝像頭對應覆蓋地區的個數
Camera[] cameraRegionsSum = new Camera[m];
// 該攝像頭是否已經被訪問過
boolean[] cameraFlag = new boolean[m];
// 已被監控的地區
boolean[] regionFlag = new boolean[n];
int lineNumber = 1;
for(int i = 0; i < n; i++) {
regions[i] = Integer.parseInt(list.get(lineNumber++));
int k = Integer.parseInt(list.get(lineNumber++));
// k地區覆蓋的攝像頭
int[] cameras = new int[k];
String[] cameraSs = list.get(lineNumber++).split(" ");
for(int j = 0; j < k; j++) {
cameras[j] = Integer.parseInt(cameraSs[j]);
// 判斷該攝像頭是否被訪問過
if(cameraFlag[cameras[j]]) {
// 如果被訪問過, 把覆蓋的地區放入集合中
List<Integer> tempList = cameraRegionsMap.get(cameras[j]);
tempList.add(regions[i]);
} else {
// 未被訪問過
List<Integer> tempList = new ArrayList<>();
tempList.add(regions[i]);
cameraRegionsMap.put(cameras[j], tempList);
// 設置爲已訪問
cameraFlag[cameras[j]] = true;
cameraRegionsSum[cameras[j]] = new Camera(cameras[j], 0);
}
// 該攝像頭覆蓋地區數量+1
cameraRegionsSum[cameras[j]].sum++;
}
}
// 移除沒監控地區的攝像頭
List<Camera> cameraList = new ArrayList<>();
for(int i = 0; i < m; i++) {
if(cameraRegionsSum[i] != null) {
cameraList.add(cameraRegionsSum[i]);
}
}
// 最後選中的攝像頭集合
List<Camera> resultList = new ArrayList<>();
int sum = 0;
do {
// 按照監控地區個數排序, 排序算法的不同會導致結果有偏差,最好使用穩定算法,例如歸併排序
Comparator<Camera> mod = (o1, o2) -> {
// 降序
return o2.sum - o1.sum;
};
cameraList.sort(mod);
resultList.add(cameraList.get(0));
List<Integer> tempList = cameraRegionsMap.get(cameraList.get(0).number);
for(int i : tempList) {
if (!regionFlag[i]) {
regionFlag[i] = true;
sum++;
}
}
for(Camera camera : cameraList) {
if (camera.sum == 0) {
break;
}
List<Integer> tempList2 = cameraRegionsMap.get(camera.number);
Iterator<Integer> it = tempList2.iterator();
while(it.hasNext()) {
Integer t = it.next();
if (regionFlag[t]) {
// 如果該地區已經被其他攝像頭監控
it.remove();
camera.sum--;
}
}
}
} while(sum != n);
System.out.println(resultList.size());
for(int i = 0; i < resultList.size(); i++) {
if(i != resultList.size() - 1) {
System.out.print(resultList.get(i).number + " ");
} else {
System.out.println(resultList.get(i).number);
}
}
}
}
ControllerFile(文件操作類)
import java.io.*;
import java.util.ArrayList;
import java.util.List;
public class ControllerFile {
public static List<String> readTxt(String txtPath) {
File file = new File(txtPath);
if (file.isFile() && file.exists()) {
try {
FileInputStream fileInputStream = new FileInputStream(file);
InputStreamReader inputStreamReader = new InputStreamReader(fileInputStream);
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
List<String> list = new ArrayList<>();
String text = null;
while ((text = bufferedReader.readLine()) != null) {
list.add(text);
}
return list;
} catch (Exception e) {
e.printStackTrace();
}
}
return null;
}
}
Main(主類)
public class Main {
public static void main(String[] args) {
long start = System.currentTimeMillis();
Coverage coverage = new Coverage();
coverage.coverageProblem();
long end = System.currentTimeMillis();
// 打印所用時間
System.out.println(end - start);
}
}
三、具體實現(C++)
待實現
四、測試樣例
測試樣例很大,如果有需要可以郵箱私我[email protected]