2021.02.06不重叠线段
题目描述
给出在数轴上的n条线段的左右端点的坐标l,r和它们的价值v,请你选出若干条没有公共点的线段(端点重合也算有公共点),使得它们的价值和最大,输出最大价值和。
输入格式
第一行一个正整数n。
接下来n行,每行三个整数l,r,v分别表示一条线段的左端点,右端点和价值。l<r,v>0。
输出格式
输出一个整数表示最大价值和。
样例输入
4
1 3 4
3 5 7
5 7 3
2 6 8
样例输出
8
数据规模和约定
n<=2000
l,r,v<=1000000
思路
【错误思路】:dp
- 按照左端点进行排序
- 定义状态dp[i]:前i条线段所能求出的最大价值和
- 状态转移方程:dp[i] = Math.max(dp[i], dp[j]+value[i]),其中第j条线段的右端点小于第i条线段的左端点
由上述思路:
上图中的5号线段的dp[5] 需要 4号线段的dp[4]推出,但是,按照定义,dp[4]应该包含了3号线段,可是,由上图可见,3号线段和5号线段是重叠的,这种思路有漏洞。
【正确思路】:dp
- 为了不使j号线段影响dp[i](i>j),可以按照线段的右端点排序
- 状态定义和转移方程和上面思路一样。
这种排序方法确保了状态转移方程的严密性和正确性。
代码
int n;
int[][] lines;
int[] dp = new int[10010];
void test() {
Scanner cin = new Scanner(System.in);
n = cin.nextInt();
//0:左端点 1:右端点 2:价值
lines = new int[n][3];
for(int i = 0; i < n; i++) {
int beg = cin.nextInt();
int end = cin.nextInt();
int v = cin.nextInt();
lines[i][0] = beg;
lines[i][1] = end;
lines[i][2] = v;
}
Arrays.sort(lines, new Comparator<int[]>() {
public int compare(int[] l1, int[] l2) {
//右端点升序-->左端点升序
return l1[1] != l2[1] ? l1[1]-l2[1] : l1[0]-l2[0];
}
});
dp[0] = lines[0][2];
for(int i = 1; i < n; i++) {
int v = lines[i][2]; //此线段的价值
dp[i] = Math.max(dp[i-1], v);
for(int j = 0; j < i; j++) {
if(lines[j][1] < lines[i][0])
dp[i] = Math.max(dp[i], dp[j]+v);
}
}
System.out.println(dp[n-1]);
}
PS:如果每条线段的价值为1,那么可以转换为 最多能选取多少条不重叠线段。此时可以用贪心算法,见2021.5.11无重叠区间。