建筑桥梁问题陈述如下:
有一条河流水平穿过一个区域。河流上下都有一组城市。河上的每个城市都与河下的城市相匹配,您可以将这种匹配作为一组配对。
您有兴趣在河对岸建造一组桥梁,以连接最大数量的匹配城市,但您必须这样做,使两座桥梁不相交。
设计一种算法来尽可能高效地解决这个问题。
我听说这个问题与longest increasing subsequence问题有关,但我不知道如何在这里使用它。例如,如果我们得到了对
2 5 8 10
6 4 1 2
那么我们为LIS考虑哪个序列?
谢谢!
答案 0 :(得分:51)
为了建立你如何使用最长的增长子序列算法来解决这个问题,让我们从一些直觉开始,然后建立一个解决方案。由于您只能在匹配索引的城市之间建立桥梁,因此您可以将最终构建的桥梁组视为您可以找到的不包含任何交叉的最大对。那么在什么情况下你会有一个交叉点?
让我们看看何时会发生这种情况。假设我们对他们的第一个城市建造的所有桥梁进行排序。如果两个桥交叉,那么我们必须有一些桥(一个 i ,b i ),这样对于其他桥(一个 j ,b j )以下其中一项成立:
第一个案例说,有一座桥,其顶部城市比我们的桥的起点更靠右边,其底部城市比我们的桥的末端更靠左边,第二个案件处理相反的情况
鉴于此属性需要保留,我们需要确保对于每组桥接器,我们确实具有以下两个属性中的一个,用于任何一对桥接器( i ,b i ),(a j ,b j ):
或
换句话说,如果我们按照第一个坐标对桥梁进行排序,那么第二个坐标的集合将一直在增加。同样,如果我们按照第二个坐标对桥梁进行排序,那么第一个坐标将一直在增加。
我们刚刚定义的属性在桥组上定义了部分排序≤ both ,我们说(a i ,b i )≤ both (a j ,b j )如果 i ≤a j 和b i ≤b j 。请注意,这不是一个总排序 - 例如,(1,2)与(2,1)无法比较 - 但它是一个部分排序,因为它是自反,反对称和传递。
鉴于此,问题的目标是找到我们可以通过这种关系完全排序的最大元素集,因为如果我们有一个包含两个无法比较的元素的集合,那么这些元素必然必须代表交叉桥。换句话说,我们希望找到部分顺序中最长的链。一种方法是在O(n 2 )时间内,将每个元素与每个其他元素进行比较,并通过≤两个查看哪些元素可以排序。这会产生有向无环图,其中对(a i ,b i )具有到(a j ,b 的边缘) j )iff(a i ,b i )≤ both (a j ,b <子>Ĵ子>)。一旦我们有了这个有向无环图,我们就可以找到longest path in the graph来找到被≤两个调用的最大元素集,然后给出问题的解决方案。因此整个运行时间为O(n 2 )。
然而,我们可以做得比这更好。上述算法的问题在于我们无法轻易地分辨元素之间的比较,因此我们必须明确地将每个城市与其他城市进行比较。
2 5 8 10
6 4 1 2
让我们按照底行对城市进行排序:
8 10 5 2
1 2 4 6
现在,这是非常酷的观察。如果我们有按其底行排序的元素,那么我们可以通过查看它们在顶行中的位置来判断两个对是否可以通过≤两个进行排序。如果第一对位于第二对的左侧,我们立即知道第一对的第二个元素小于第二对的第二个元素,因为我们已经按第二个坐标对它们进行了排序。然后,如果第一对的第一个元素小于第二个对的第一个元素,则可以将这对元素组合在一起。因此,如果我们想要找到一组可以一起构建的桥,我们正在寻找顶行的增加子序列,因为在这种情况下,当我们离开时,对的第一个和第二个元素都在增加。向左走。找到增长最长的子序列然后解决这个问题。既然我们可以通过O(n log n)中的第二个字段对这些对进行排序,并在O(n log n)中找到最长的增加子序列,那么这就是问题的O(n log n)解决方案!
呼!希望这个答案能够详细解释事情!
答案 1 :(得分:14)
首先考虑对:(2,6), (5, 4), (8, 1), (10, 2)
,根据对的第一个元素对它进行排序(在这种情况下已经排序)并计算对的第二个元素的列表,从而计算{ {1}},即6 4 1 2
。因此,您要查找的非重叠行是1 2
和(8, 1)
。
答案 2 :(得分:5)
这是问题的Java实现。
package DP;
import java.util.Arrays;
import java.util.Comparator;
public class BuildingBridges {
public static void main(String[] args) {
Pair[] A = new Pair[7];
A[0] = new Pair(22,4);
A[1] = new Pair(2,6);
A[2] = new Pair(10,3);
A[3] = new Pair(15,12);
A[4] = new Pair(9,8);
A[5] = new Pair(17,17);
A[6] = new Pair(4,2);
System.out.println(lis(A));
}
public static int lis(Pair[] A){
Arrays.sort(A, new Comparator<Pair>() {
@Override
public int compare(Pair o1, Pair o2) {
return o1.x - o2.x;
}
});
int n = A.length;
int max = 0;
int[] dp = new int[n];
Arrays.fill(dp, 1);
for(int i=1; i<n; i++){
for(int j=0; j<i; j++){
if(A[i].y > A[j].y){
dp[i] = Math.max(dp[i], dp[j]+1);
}
}
max = Math.max(max, dp[i]);
}
return max;
}
public static class Pair{
int x, y;
public Pair(int x_, int y_){
x = x_;
y = y_;
}
}
}
答案 3 :(得分:4)
这是一个动态编程问题,即使是最长的子序列问题也可以建模。考虑河流北部城市的坐标为a1,a2,a3..aN。现在找到河流南部的相应城市,并将它们标记为a1,a2,a3..aN。 这个问题的解决方案将是由河流的北部和南部的aI形成的弦的最长的共同子序列。
答案 4 :(得分:1)
对一个列表进行排序,并在other.C ++ code-&gt;
中找到LIS#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
bool cmp(pair<int,int> a, pair<int,int> b){
return a.first<b.first;
}
int bridges(vector<pair<int,int> > connect){
int i, j, n=connect.size();
sort(connect.begin(),connect.end(),cmp);
vector<int> lis(n,1);
int m=0;
for(i=0;i<n;i++){
for(j=i-1;j>=0;j--){
if(connect[i].second>connect[i].first)lis[i]=max(lis[i],lis[j]+1);
}
m=max(m,lis[i]);
}
return m;
}
int main(){
int n, i;
cin >> n;
vector<pair<int,int> > a(n);
for(i=0;i<n;i++)cin >> a[i].first >> a[i].second;
cout << bridges(a) <<endl;
return 0;
}
答案 5 :(得分:0)
这是用于解决上述问题的c ++中的工作代码。
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
struct pairs{
int x;
int y;
};
bool myf(struct pairs p1,struct pairs p2){
return p1.x<p2.x;
}
int lis(struct pairs a[],int n){
sort(a,a+n,myf);
int lis[n];
for(int i=0;i<n;i++)
lis[i]=1;
for(int i=1;i<n;i++){
for(int j=0;j<i;j++){
if((a[j].y<a[i].y)&&(lis[i]<lis[j]+1))
lis[i]=lis[j]+1;
}
}
int max=lis[0];
for(int i=1;i<n;i++){
if(max<lis[i])
max=lis[i];
}
return max;
}
int main()
{
struct pairs arr[100];
int n;
cin>>n;
for(int i=0;i<n;i++){
cin>>arr[i].x>>arr[i].y;
}
int max=lis(arr,n);
cout<<max<<"\n";
return 0;
}