《算法竞赛·快冲300题》每日一题:“最大团”

算法竞赛·快冲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)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

罗勇军

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值