《算法竞赛·快冲300题》将于2024年出版,是《算法竞赛》的辅助练习册。
所有题目放在自建的OJ New Online Judge。
用C/C++、Java、Python三种语言给出代码,以中低档题为主,适合入门、进阶。
“ 最大团” ,链接: http://oj.ecustacm.cn/problem.php?id=1762
题目描述
【题目描述】 数轴上有n个点,第i个点的坐标为xi,权值为wi。两个点i,j之间存在一条边当且仅当abs(xi-xj)>=wi+wj。
你需要求出这张图的最大团的点数。
团:两两之间存在边的定点集合。
【输入格式】 输入第一行为n(n≤200000)
接下来n行,每行两个整数xi、wi。(0≤|xi|,wi≤10^9)
【输出格式】 输出一行一个整数,表示最大团的点数。
【输入样例】
4
2 3
3 1
6 1
0 2
【输出样例】
3
题解
最大团是一个图论问题:在一个无向图中找出一个点数最多的完全子图。所谓完全图,就是图中所有的点之间都有边。n个点互相连接,共有n(n-1)/2条边。
普通图上的最大团问题是NP问题,计算复杂度是指数级的。例如常见的Bron-Kerbosch算法,是一个暴力搜索算法,复杂度
O
(
3
n
/
3
)
O(3^{n/3)}
O(3n/3)。所以如果出最大团的题目,一般不会在普通图上求最大团,而是在一些特殊的图上,用巧妙的、非指数复杂度的算法求最大团。本题n≤200000,只能用复杂度小于O(nlogn)的巧妙算法。
本题的图比较特殊,所有点都在直线上。在样例中,存在的边有(0-3)、(0-6)、(2-6)、(3-6),其中{0, 3, 6}这3点之间都有边,它们构成了一个最大团。
另外,本题对边的定义也很奇怪:“两个点i, j之间存在一条边当且仅当abs(xi-xj)≥wi+wj”。
考虑以下2个问题:
(1)哪些点之间有边?题目的定义是abs(xi-xj)≥wi+wj,若事先把x排了序,设xj≥xi,移位得xj-wj≥xi+wi。这样就把每个点的x和w统一起来了,方便计算。
(2)哪些点构成了团?是最大团吗?考察3个点x1≤x2≤x3,若x1和x2有边,则应有x1+w1≤x2-w2;若x2和x3有边,则x2+w2≤x3-w3。推导x1和x3的关系:x1+w1≤x2-w2≤x2+w2≤x3-w3,即x1+w1≤x3-w3,说明x1和x3也有边。x1、x2、x3这3点之间都有边,它们构成了一个团。依次这样操作,得符合条件的x1、x2、x3、…,构成了一个大的团。但是,用这个方法得到团是最大的吗?
为了方便思考,把上述讨论画成图。
把每个点的信息画成线段,左端点是x-w,右端点是x+w。问题建模为:在n个线段中,找出最多的线段,使得它们互相不交叉。
如果学过贪心,发现它就是“活动安排问题(参考《算法竞赛入门到进阶》 100页,活动安排问题)”,或者“区间调度问题”,即在n个活动中,安排尽量多的活动,使得互相不冲突。贪心解法是:
(1)按活动的结束时间排序。本题按x+w排序。
(2)选择第一个结束的活动,跳过与它时间冲突的活动。
(3)重复(2),直到活动为空。每次选择剩下活动中最早结束的活动,并跳过与它冲突的活动。
【重点】 贪心的建模 。
C++代码
代码的计算复杂度,排序O(nlogn),贪心O(n),总复杂度O(nlogn)。
#include<bits/stdc++.h>
using namespace std;
const int N = 200005;
struct aa{ int l, r; } a[N];
bool cmp(const aa &a,const aa &b){ return a.r<b.r;} //比较右端点
int main(){
int n; scanf("%d",&n);
for (int i=1;i<=n;i++){
int x,w; scanf("%d%d",&x,&w);
a[i].l = x-w,
a[i].r = x+w;
}
sort(a+1,a+n+1,cmp); //按右端点排序
int R=a[1].r, ans=1;
for (int i=2;i<=n;i++) //选剩下活动中最早结束的活动,跳过冲突的活动
if (a[i].l>=R){
R = a[i].r;
ans++;
}
printf("%d",ans);
return 0;
}
Java代码
import java.util.*;
class Main {
static class aa { int l, r; }
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
aa[] a = new aa[n + 1];
for (int i = 1; i <= n; i++) {
int x = sc.nextInt();
int w = sc.nextInt();
a[i] = new aa();
a[i].l = x - w;
a[i].r = x + w;
}
Arrays.sort(a, 1, n + 1, new Comparator<aa>() {
public int compare(aa a, aa b) { return a.r - b.r; }
});
int R = a[1].r;
int ans = 1;
for (int i = 2; i <= n; i++) {
if (a[i].l >= R) {
R = a[i].r;
ans++;
}
}
System.out.println(ans);
}
}
Python代码
n = int(input())
a = []
for i in range(n):
x, w = map(int, input().split())
a.append({'l': x-w, 'r': x+w})
a.sort(key=lambda x: x['r'])
R, ans = a[0]['r'], 1
for i in range(1, n):
if a[i]['l'] >= R:
R = a[i]['r']
ans += 1
print(ans)