洛谷P3699 [CQOI2017] 小Q的草稿

题面分析

形式化地说,一个平面中有 \(T\) 个互不重叠的三角形和 \(V\) 个关键点(任意三个关键点都不共线),求有多少对关键点之间的线段不会穿过三角形。

注意到这道题的数据很小 \((T,V \le 1000)\) 且时间有 \(3s\),我们可以想到很朴素的 \(O(TV^2)\) 做法,即遍历每个三角形的每条边,枚举两个点 \(X,Y\) 判断线段 \(XY\) 是否与该边相交。

关于具体实现

数据较小,选择利用邻接矩阵维护两点之间是否可以连线。

我们遍历每个三角形的每条边,以该边所在的直线为界限对所有关键点进行分类,分成三类:直线的一边、直线另一边、直线上。

具体到分类的方法上,我们给出一张图,如下:

对于线段 \(AB\),我们可以根据 \(A、B\) 两点的坐标确定上图中的六个区域。

我们称点 \(C\) 在直线的左侧,\(D\) 在直线的右侧。其中区域 \(2、3、4\) 明显在直线左侧,区域 \(1、6、5\) 则被直线 \(AB\) 分割。

若点 \(E\) 落在区域 \(1\) 中,计算直线 \(AB、EA、EB\) 的斜率 \(k、k1、k2\),容易发现:

  • \(E\) 在直线左侧,则 \(k > k2 > k1\)
  • \(E\) 在直线右侧,则 \(k < k2 < k1\)
  • \(E\) 在直线上,则 \(k = k2 = k1\)

根据以上规则就能对点 \(E\) 进行分类,落在区域 \(5、6\) 的点也可以用类似的方法分类。

分完类后,遍历每两个点,在两个点分别处于直线左侧和右侧或同在直线上时,若没有标记,判断两点的连线是否与当前线段相交,若相交就打上标记记录为不可连线。

遍历完所有三角形后,遍历邻接矩阵统计答案。

一些优化(常数)

  1. 我们不需要遍历每一对点,可以临时存储分类的结果遍历临时存储的内容。这样可以把 \(O(T^2)\) 的遍历优化为 \(O(\frac{1}{4}T^2)\)
  2. 统计答案可以用容斥原理统计。

Code

代码没有使用上述优化方案,仍能以较宽裕时间通过此题。

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define db double

db rd() //快读省略

int n,m;
struct node
{
    db x,y;
}a[1005];
struct tri
{
    db x1,y1,x2,y2,x3,y3;
}t[1005];
bool vis[1005][1005];
int col[1005];
int ans;
const db eps = 1e-8;

db K(node x,node y){ return (db)(y.y - x.y) / (db)(y.x - x.x); }
db crossx(node p1,node p2,node p3,node p4)
{
    auto [x1,y1] = p1;
    auto [x2,y2] = p2;
    auto [x3,y3] = p3;
    auto [x4,y4] = p4;
    db denominator = (y2 - y1) * (x3 - x4) - (y4 - y3) * (x1 - x2);
    db x = ((x1 - x2) * (x4 * y3 - x3 * y4) - (x3 - x4) * (x2 * y1 - x1 * y2)) / denominator;
    return x;
}

void solve(node p1,node p2)
{
    if(p1.x > p2.x) swap(p1,p2);
    if(p1.x == p2.x)
    {
        for(int i = 1;i <= n;i++)
        {
            auto [x,y] = a[i];
            if(x < p1.x) col[i] = 1;
            else if(x > p1.x) col[i] = 2;
            else col[i] = 0;
        }
        for(int i = 1;i <= n;i++) for(int j = i + 1;j <= n;j++) if(!vis[i][j])
        {
            if(!col[i] && !col[j] && ((a[i].x > p2.x && a[j].x < p2.x) || (a[i].x < p1.x && a[j].x > p1.x))) vis[i][j] = vis[j][i] = 1;
            if(col[i] == col[j]) continue;
            db x = crossx(p1,p2,a[i],a[j]);
            if(p1.x - eps <= x && x <= p2.x + eps) vis[i][j] = vis[j][i] = 1;
        }
        return;
    }
    db k = K(p1,p2);
    if(k == 0)
    {
        for(int i = 1;i <= n;i++)
        {
            auto [x,y] = a[i];
            if(y > p1.y) col[i] = 1;
            else if(y < p1.y) col[i] = 2;
            else col[i] = 0;
        }
        for(int i = 1;i <= n;i++) for(int j = i + 1;j <= n;j++) if(!vis[i][j])
        {
            if(!col[i] && !col[j] && ((a[i].x > p2.x && a[j].x < p2.x) || (a[i].x < p1.x && a[j].x > p1.x))) vis[i][j] = vis[j][i] = 1;
            if(col[i] == col[j]) continue;
            db x = crossx(p1,p2,a[i],a[j]);
            if(p1.x - eps <= x && x <= p2.x + eps) vis[i][j] = vis[j][i] = 1;
        }
    }
    else if(k > 0)
    {
        for(int i = 1;i <= n;i++)
        {
            auto [x,y] = a[i];
            db k1 = K(p1,a[i]),k2 = K(p2,a[i]);
            if(fabs(k1 - k2) < eps) col[i] = 0;
            else if((x < p1.x && y > p2.y) || (x >= p1.x && x <= p2.x && y > p2.y) || (y >= p1.y && y <= p2.y && x < p1.x)) col[i] = 1;
            else if((x > p2.x && y > p2.y && k1 > k - eps && k2 > k - eps) || (x < p1.x && y < p1.y && k1 < k + eps && k2 < k + eps)) col[i] = 1;
            else if(x >= p1.x && x <= p2.x && y >= p1.y && y <= p2.y && k1 > k - eps && k2 < k + eps) col[i] = 1;
            else col[i] = 2;
        }
        for(int i = 1;i <= n;i++) for(int j = i + 1;j <= n;j++) if(!vis[i][j])
        {
            if(!col[i] && !col[j] && ((a[i].x > p2.x && a[j].x < p2.x) || (a[i].x < p1.x && a[j].x > p1.x))) vis[i][j] = vis[j][i] = 1;
            if(col[i] == col[j]) continue;
            db x = crossx(p1,p2,a[i],a[j]);
            if(p1.x - eps <= x && x <= p2.x + eps) vis[i][j] = vis[j][i] = 1;
        }
    }
    else
    {
        for(int i = 1;i <= n;i++)
        {
            auto [x,y] = a[i];
            db k1 = K(p1,a[i]),k2 = K(p2,a[i]);
            if(fabs(k1 - k2) < eps) col[i] = 0;
            else if((x < p1.x && y < p2.y) || (x >= p1.x && x <= p2.x && y < p2.y) || (y <= p1.y && y >= p2.y && x < p1.x)) col[i] = 1;
            else if((x > p2.x && y < p2.y && k1 < k + eps && k2 < k + eps) || (x < p1.x && y > p1.y && k1 > k - eps && k2 > k - eps)) col[i] = 1;
            else if(x >= p1.x && x <= p2.x && y <= p1.y && y >= p2.y && k1 < k + eps && k2 > k - eps) col[i] = 1;
            else col[i] = 2;
        }
        for(int i = 1;i <= n;i++) for(int j = i + 1;j <= n;j++) if(!vis[i][j])
        {
            if(!col[i] && !col[j] && ((a[i].x > p2.x && a[j].x < p2.x) || (a[i].x < p1.x && a[j].x > p1.x))) vis[i][j] = vis[j][i] = 1;
            if(col[i] == col[j]) continue;
            db x = crossx(p1,p2,a[i],a[j]);
            if(p1.x - eps <= x && x <= p2.x + eps) vis[i][j] = vis[j][i] = 1;
        }
    }
}

signed main()
{
    n = rd(),m = rd();
    for(int i = 1;i <= n;i++) a[i] = {rd(),rd()};
    for(int i = 1;i <= m;i++) t[i] = {rd(),rd(),rd(),rd(),rd(),rd()};
    for(int i = 1;i <= m;i++)
    {
        node p1 = {t[i].x1,t[i].y1},p2 = {t[i].x2,t[i].y2},p3 = {t[i].x3,t[i].y3};
        solve(p1,p2);
        solve(p2,p3);
        solve(p1,p3);
    }
    int ans = 0;
    for(int i = 1;i <= n;i++) for(int j = i + 1;j <= n;j++) if(!vis[i][j]) ans++;
    cout << ans;
    return 0;
}

本以为会卡过去,没想到最慢的点也只用了时限的一半。

posted @ 2025-06-15 18:56  IC0CI  阅读(6)  评论(0)    收藏  举报