一个包含n个圆心,每个圆半径都为r的位置坐标文件,你设计一个算法,找出m个圆心,使这m个圆总覆盖区域最大。
换个意思就是选择m个交集最小的圆
step1: 构建图:圆心为节点,有交集添边,边权为交集面积
step2: 简单bfs算法。目的把图分割为一个个连通图
step3: 直接选择孤立的节点.也就是不相交的节点。假设选了L个
step4: 若L >= m。则返回前m个圆心
step5: 把每组连通图贪心的删除节点。如包含p个节点的连通图,删除和其它圆交集最大圆,构成一个p-1个节点的子图。这个子图再删除一个相交最大圆,构成p-2个节点的子图。以此类推。 这些子图组成一组,体积v为节点个数,代价w为交集面积。
step6: 分组背包:背包体积V=m-L,按照step5分好的组装入背包。记录状态转移过程。对于每个w修改为max_w - w。
我们先来看分组背包问题
有N件物品和一个容量为V的背包,第i件物品的重量为c[i],价值为w[i],这些物品被划分成了若干组,每组中的物品互相冲突,最多选一件
问将哪些物品放入背包中可以使背包获得最大的价值
我们用f[k][v]表示前k种物品花费费用v所能取得的最大价值
给出状态转移方程:
f[k][v]=max{f[k-1][v],f[k-1][v-c[i]]+w[i]|物品i属于第k组}
实现:
//注意这里是三层循环
for 所有的组k
for v=V to 0
for 所有属于组k的i
f[v]=max{f[v],f[v-c[i]]+w[i]}
end
end
end
选择的节点就是孤立节点+分组背包选择的节点。
极限情况下,可能会 连通图很大,没有孤立节点,这样V就会很大,每一组的遍历也会很多。step5应该可以优化一下。优化情况有空再想想。
python代码实现:
import numpy as np
import math
def load_data(path):
data = np.loadtxt(path,dtype=np.float,delimiter=",")
return data
def intersection(a,b,r):
p = a - b
d = math.hypot(p[0],p[1])
if d == 0:
return np.pi*r*r
if d < 2*r:
ang=2*np.arccos((r*r+d*d-r*r)/(2*r*d));
s = ang*r*r - r*d*np.sin(ang)
return s
return 0
def bfs(seed, adj, visited):
group = [seed]
que = [seed]
S = 0
while len(que)>0:
f = que.pop(0)
for neighbor,s in adj[f]:
if visited[neighbor] != 1:
visited[neighbor] = 1
group.append(neighbor)
S += s
que.append(neighbor)
return group, S
def subGroup(group,adj):
sloss = []
for u in group:
sloss.append((u,np.sum([s for v,s in adj[u]])))
sloss.sort(key=lambda x:x[1], reverse=True)
def getLoss(nodes, adj):
rets = 0
for u in nodes:
for v,s in adj[u]:
if v in nodes:
rets += s
return rets/2
retGroups = []
for i in range(0,len(sloss)):
nodes = [v for v,s in sloss[i:]]
retGroups.append([nodes, getLoss(nodes, adj)])
return retGroups
def DP(gs, V):
T = len(gs)
maxS = -1
for i in range(0,T):
for g,s in gs[i]:
maxS = max(maxS,s)
for i in range(0,T):
for k in range(0,len(gs[i])):
gs[i][k][1] = maxS - gs[i][k][1]
dps = np.zeros(V+1)
pSet = {i:[] for i in range(V+1)}
for i in range(T):
for j in range(V,0,-1):
choiced = None
for k in range(len(gs[i])):
v = len(gs[i][k][0])
w = gs[i][k][1]
if j - v >= 0:
comp = dps[j - v] + w
if dps[j]< comp:
choiced = k
dps[j] = comp
pSet[j].append(choiced)
retNodes = []
for i in range(T):
num = pSet[V][i]
if num != None:
retNodes.extend(gs[i][num][0])
return retNodes
def solution(data,r,n):
adj = {i:[] for i in range(data.shape[0])}
for i in range(data.shape[0]):
for j in range(i+1,data.shape[0]):
s = intersection(data[i,:],data[j,:],r)
if s>0:
adj[i].append((j,s))
adj[j].append((i,s))
visited = np.zeros(data.shape[0],dtype = np.int)
groups = []
for i in range(data.shape[0]):
if visited[i] == 0:
visited[i] = 1
group, S = bfs(i,adj,visited)
groups.append((group,S))
groups.sort(key=lambda x:x[1])
choiced = [groups[i][0][0] for i in range(len(groups)) if len(groups[i][0])==1]
choiced_n = len(choiced)
if choiced_n >= n:
return choiced[:n]
ngroups = []
for i in range(choiced_n,len(groups)):
group = groups[i]
ngroups.append(subGroup(group[0], adj))
retDP = DP(ngroups,n-choiced_n)
choiced.extend(retDP)
return choiced
from matplotlib.patches import Ellipse, Circle
import matplotlib.pyplot as plt
def visualization(nodes, coordinates, r):
fig=plt.figure(1)
xmax = None
xmin = None
ymax = None
ymin = None
for x,y in coordinates:
xmax = x if xmax==None or x > xmax else xmax
xmin = x if xmin==None or x < xmin else xmin
ymax = y if ymax==None or y > ymax else ymax
ymin = y if ymin==None or y < ymin else ymin
plt.axis([xmin,xmax,ymin,ymax])
ax=fig.add_subplot(1,1,1)
nSet = set(nodes)
for i in range(coordinates.shape[0]):
x,y = coordinates[i]
if i in nSet:
cir = Circle(xy = (x, y), radius=r, color='r', alpha=0.5)
else:
cir = Circle(xy = (x, y), radius=r, color='g', alpha=0.5)
ax.add_patch(cir)
plt.axis('scaled')
plt.show()
if __name__ == "__main__":
data = load_data('./GPS.csv')
r = float(input("input r:"))
n = int(input("how many nodes would you want to choice? input n:"))
if n > data.shape[0]:
print("too many n!")
nodes = solution(data,r,n)
visualization(nodes,data,r)
可视化看一看:
红色为选择,绿色不选。
可以看到我们选择的红色圆都躲避了“高密度”地区。
测试数据的话。自己可以用随机数生成一下。