洛谷P7918 【洛谷月赛LGR-096 Div.2 T2】 [Kubic] Lines题解

16 篇文章 6 订阅

这道题是普及-,结果我比赛时想了整整一个小时。。。果然我还是太了啊QAQ

题目大意

在这里插入图片描述

解题思路

佬曰:“有人看到这道题直接网络流。”

网络流?最大独立集?大可不必。题目中最关键的一句话其实是

注意:输入数据不保证 gcd ⁡ ( a , b ) = 1 \gcd(a,b)=1 gcd(a,b)=1

真是一语点醒梦中人。事实上,我们需要充分利用 a a a, b b b是整数的条件。两条直线 a i x + b i y + c i = 0 a_ix+b_iy+c_i=0 aix+biy+ci=0 a j x + b j y + c j = 0 a_jx+b_jy+c_j=0 ajx+bjy+cj=0平行,当且仅当 a i b j = a j b i a_ib_j=a_jb_i aibj=ajbi(或者说它们斜率相等)。如果我们将每对 ( a i , b i ) (a_i,b_i) (ai,bi)预处理成 ( a i gcd ⁡ ( a i , b i ) , b i gcd ⁡ ( a i , b i ) ) (\frac{a_i}{\gcd(a_i,b_i)},\frac{b_i}{\gcd(a_i,b_i)}) (gcd(ai,bi)ai,gcd(ai,bi)bi)(如果 gcd ⁡ ( a i , b i ) ≠ 0 \gcd(a_i,b_i)\ne 0 gcd(ai,bi)=0),那么平行的直线其 ( a i gcd ⁡ ( a i , b i ) , b i gcd ⁡ ( a i , b i ) ) (\frac{a_i}{\gcd(a_i,b_i)},\frac{b_i}{\gcd(a_i,b_i)}) (gcd(ai,bi)ai,gcd(ai,bi)bi)是相等的(例如 3 x + 5 y + 3 = 0 3x+5y+3=0 3x+5y+3=0 9 x + 15 y + 8 = 0 9x+15y+8=0 9x+15y+8=0,都可以处理成 ( 3 , 5 ) (3,5) (3,5))。每条直线的斜率都被归约为互质的数对。而这样的数对不相等的直线一定会相交,因为斜率不相等。我们将平行的直线合并为一“”,假设共有 k k k类,两类直线之间一定会有交点。假设每类所含直线个数分别为 d 1 , d 2 , … , d k d_1,d_2,\dots,d_k d1,d2,,dk
我比赛时的想法是:总的交点个数为 ∑ i = 1 k − 1 ∑ j = i + 1 k d i d j = ( d 1 + d 2 + ⋯ + d k ) 2 − ( d 1 2 + d 2 2 + ⋯ + d k 2 ) 2 \sum \limits_{i=1}^{k-1} \sum \limits_{j=i+1}^kd_id_j=\frac{(d_1+d_2+\dots+d_k)^2-(d_1^2+d_2^2+\dots+d_k^2)}{2} i=1k1j=i+1kdidj=2(d1+d2++dk)2(d12+d22++dk2)。按 d i d_i di从小到大选取覆盖的直线,每次能覆盖 d i ( d i + 1 + d i + 2 + ⋯ + d n ) d_i(d_{i+1}+d_{i+2}+\dots+d_n) di(di+1+di+2++dn)个点。直到能覆盖到所有交点为止。
正解: 后来一想,发现其实答案是 n − max i = 1 n d i n- \text{max}_{i=1}^nd_i nmaxi=1ndi。因为既然要覆盖所有的点,只能有一类直线不被选取,其他类的直线必须被选取。那么就舍弃数量最多的一类直线。
关于只能有一类直线被舍弃,可以用反证法。假如有两类直线都被舍弃,则这两类之间的交点无法被任何其他类的直线覆盖,所以最多只能舍弃一类。

时间复杂度: O ( n log ⁡ n ) O(n \log n) O(nlogn)

代码实现

代码中,我们使用mpstd::map<pair<ll, ll>, ll>类型)来统计数对为pair<ll, ll>的直线的个数(即 d i d_i di)。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cstdlib>
#include <queue>
#include <map>
#include <functional>

using namespace std;

typedef long long ll;
typedef unsigned long long ull;

inline ll read() // 快速读入
{
    ll ret = 0;
    char sgn = 0;
    char c;
    while(!isdigit(c = getchar())) if(c == '-') sgn = 1;
    ret = c ^ 48;
    while(isdigit(c = getchar())) ret = (ret << 1) + (ret << 3) + (c ^ 48);
    return sgn ? -ret : ret;
}

const int MAXN = 1e5 + 5;
ll n, a[MAXN], b[MAXN], c[MAXN]; // a[i]x + b[i]y + c[i] = 0
map<pair<ll, ll>, ll> mp;

ll gcd(ll ai, ll bi) // 最大公约数
{
    return bi ? gcd(bi, ai % bi) : ai;
}

void ins(ll ai, ll bi) // 插入一条直线
{
    ll g = gcd(ai, bi);
    if(!g) g = 1;
    // 如果最大公约数为0,就将ai, bi同时除1,相当于啥都没干
    pair<ll, ll> p(ai / g, bi / g);
    // 找到直线对应的数对(每类直线数对相等)
    if(mp.find(p) == mp.end()) // 如果mp中没有找到该数对
        mp[p] = 1; // 新建该数对(新建一类),数量为1
    else ++mp[p]; // 否则该类直线个数+1
}

int main()
{
    n = read();
    for(ll i = 1; i <= n; ++i)
        a[i] = read(), b[i] = read(), ins(a[i], b[i]), c[i] = read();
    ll ans = 0;
    for(map<pair<ll, ll>, ll>::iterator i = mp.begin(); i != mp.end(); ++i)
        ans = max(ans, i->second); // 找到数量最多的一类直线
    cout << n - ans << '\n'; // 输出答案
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值