hihocoder 1569 [Offer收割]编程练习赛25 : 无限巧克力谜题

1569 : 无限巧克力谜题

[ 题目链接 ]

时间限制:10000ms
单点时限:1000ms
内存限制:256MB
描述

小Ho在网上看到如下无限切分巧克力的方法,百思不得其解,于是去请教小Hi。

当然这种小把戏迷惑不了小Hi,他一眼就看出实际上拼接后的巧克力之间是有缝隙的。

为了帮助小Ho,小Hi决定写一个程序训练小Ho的平面几何观察力:给定N个三角形,判断这N个三角形能否恰好拼成一个A × B的矩形(矩形的边与坐标轴平行)。

输入
第一行包含一个整数T,代表测试数据的组数。

对于每组测试数据,第一行包含三个整数N, A, B,代表三角形的个数和长宽。

以下N行每行6个整数(X1, Y1), (X2, Y2), (X3, Y3)代表其中一个三角形的三个顶点的坐标。

注意三角形可以平移,但是不能旋转和翻转。

对于50%的数据,N = 3

对于100%的数据,3 ≤ N ≤ 4, 1 ≤ A, B ≤ 100000, -100000 ≤ Xi, Yi ≤ 100000

输出
对于每组数据输出一行YES或者NO代表是否能恰好拼成一个A × B的矩形。

样例输入
2
4 3 3
0 0 0 -3 1 -2
0 0 1 1 3 0
0 0 -1 2 2 -1
0 0 -3 0 0 -3
4 3 3
0 0 1 1 3 0
0 0 1 1 3 0
0 0 -1 2 2 -1
0 0 -3 0 0 -3
样例输出
YES
NO

题目分析

  1. 三角形的某一点必在矩形顶点上

第一刀情况:

  • 情况一:经过矩形的两个顶点

hihocoder 1569 [Offer收割]编程练习赛25 : 无限巧克力谜题_第1张图片

  • 情况二:经过矩形的一个顶点

hihocoder 1569 [Offer收割]编程练习赛25 : 无限巧克力谜题_第2张图片

  • 情况三:不经过矩形的任何一个顶点

hihocoder 1569 [Offer收割]编程练习赛25 : 无限巧克力谜题_第3张图片

对于上面三种情形,想要分出3个或4个三角形,产生的三角形的某一条边肯定和矩形的某一边共边,并且这条边的其中一个顶点是矩形的顶点。
综上,得出的结论:3个或4个三角形想要拼成一个矩形,则每个三角形的某个顶点肯定和矩形的顶点共点。

所以,我们可以假设矩形的左下角位置是(0,0)右上角位置为(A,B),然后枚举每一个三角形的某一点,将其放在矩形的某个点上(三角形超出矩形边界的情况除外,程序中很好判断)。一共是(3*4)^4=20736然后判断矩形内部是否都被覆盖,并且每个点只被一块三角形覆盖。

2.如何判断矩形内部是否都被覆盖,并且每个点只被一块三角形覆盖。

这个分为两步:1判断矩形是否都被覆盖,2判断三角形面积之和是否等于矩形面积。

对于第二点很好判断,直接利用公式对面积求和就可以。

我们重点来看看第1步怎么做。
在这里要引入向量,如果从某个点出发有一个向量,那么说明存在一条从该点出发并且方向与向量相同的一条边。
一条线段会产生出两个向量,分别从两端出发,并且方向相反。
如果从某个点出发的同一个方向向量有两个,有两种可能:

1.两三角形不重合
hihocoder 1569 [Offer收割]编程练习赛25 : 无限巧克力谜题_第4张图片

2.两三角形有重合区域
hihocoder 1569 [Offer收割]编程练习赛25 : 无限巧克力谜题_第5张图片

不管哪种情况,我们都进行异或操作,即原来有就去掉,没有就加上。

很显然,用向量来表示长宽为A,B的矩形的话就是

从(0,0)出发的向量有 (A,0)和(0,B)
从(0,B)出发的向量有 (A,0)和(0,-B)
从(A,0)出发的向量有 (-A,0)和(0,B)
从(A,B)出发的向量有 (-A,0)和(0,-B)

可以对上面的向量进行最简化。
如图

hihocoder 1569 [Offer收割]编程练习赛25 : 无限巧克力谜题_第6张图片

所以,我们可以把三角形放置好后判断三角形的每点顶出发的向量,是否除了表示矩形的向量,没有其他的向量了。如果是,则说明矩形已经被完全覆盖,否则没有完全被覆盖。

向量处理:为了方向计算,我们把从同一点出发并且平行的向量视为相等的向量。
在计算过程中可以对向量进行最简化,即除他们的最大公约数。然后正向化(在程序里有注释)。

c++代码如下

#include 
#include 
#include
#include
using namespace std;

typedef long long lld;
int A, B;
int tri[10][10];

mapint, int> , setint, int> > > mp;

bool escape=false;//是否找到可行方案

//平移并判断三角形是否超出边界
bool changeTo(int cnt, int x, int tox, int toy)
{
    int dx = tox- tri[cnt][x*2];
    int dy = toy - tri[cnt][x*2+1];

    bool ans=true;
    for(int i=0;i<3;++i)
    {
        tri[cnt][i*2]+=dx;
        tri[cnt][i*2+1]+=dy;

        if(tri[cnt][i*2]<0 || tri[cnt][i*2]>A) ans=false;
        if(tri[cnt][i*2+1]<0 || tri[cnt][i*2+1]>B) ans=false;
    }

    return ans;
}

int dir[4][2]={{1,1}, {1, 0}, {0, 1}, {0, 0}};

int getlcm(int a, int b)
{
    if(a==0 || b==0)return max(a, b);
    if(a%b==0)return b;

    return getlcm(b, a%b);
}

//添加向量
void addVec(int cnt, int i)
{
    int dx = tri[cnt][i*2];
    int dy= tri[cnt][i*2+1];

    pair<int, int>  op = {dx, dy};

    for(int k=0;k<3;++k)
    {
        if(k==i)continue;

        int nx = tri[cnt][k*2];
        int ny= tri[cnt][k*2+1];
        pair<int, int> p = make_pair(dx-nx, dy-ny);

        //最小化
        int lcm = getlcm(abs(p.first), abs(p.second));
        p.first/=lcm;
        p.second/=lcm;

        //正向化
        if(p.first<0)
        {
            p.first*=-1;
            p.second*=-1;
        }
        else if(p.first*p.second==0)
        {
            p.first=abs(p.first);
            p.second = abs(p.second);
        }

        //异或操作
        if(mp[op].count(p)==1) mp[op].erase(p);
        else mp[op].insert(p);
    }
}

void dfs(int cnt, int n)
{
    if(cnt==n)
    {
        bool ans=true;

        for(auto it : mp)//查看每个顶点
        {
            auto point = it.first;
            int x=point.first;
            int y=point.second;
            if((x==0 && y==0) || (x==0 && y== B) || (x==A && y==0) || (x==A && y==B))//是矩形的顶点
            {

                if(it.second.size()!=2)//有且仅有两个条边
                {
                    ans=false;
                    break;
                }

                if(it.second.count({0, 1})==0 || it.second.count({1, 0})==0){//一定是水平和竖直边
                    ans=false;
                    break;
                }
            }
            else//其他顶点
            {
                if(it.second.size()!=0)//没有边
                {
                    ans=false;
                    break;
                }
            }
        }

        if(ans)escape=true;
        return;
    }

    if(escape)return;//已经有了可行方案停止搜索

    for(int i=0;i<3;++i)//对第cnt个三角形第i个点
    {
        for(int j=0;j<4;++j)//放置在矩形的某一点
        {
            if(!changeTo(cnt, i, dir[j][0]*A, dir[j][1]*B))continue;//平衡并判断三角形是否超出边界
            for(int s=0;s<3;++s)addVec(cnt, s);//为三角形的每一条边添加边
            dfs(cnt+1, n);
            for(int s=0;s<3;++s)addVec(cnt, s);//恢复添加的边,由于是异或操作,所以调用的方法相同
            if(escape)return;//已经有了可行方案停止搜索
        }
    }
}

//根据公式计算面积
lld getArea(int cnt)
{
    //x1*(y2-y3) + x2*(y3-y1) + x3*(y1-y2)
    lld sum=0;
    sum += 1L * tri[cnt][0]*(tri[cnt][3]-tri[cnt][5]);
    sum += 1L * tri[cnt][2]*(tri[cnt][5]-tri[cnt][1]);
    sum += 1L * tri[cnt][4]*(tri[cnt][1]-tri[cnt][3]);

    return abs(sum);//要取正数
}

void solve()
{
    int t;
    scanf("%d", &t);

    while(t--)
    {
        int n;
        scanf("%d%d%d", &n, &A, &B);

        lld sumArea = 0;
        for(int i=0;ifor(int j=0;j<6;++j)scanf("%d", tri[i]+j);
            sumArea += getArea(i);
        }

        lld targetArea = 2L * A * B;//矩形面积,求面积时没有除2,所以要乘以2
        if(sumArea != targetArea)//所有三角形面积之和是否等于矩形
        {
            puts("NO");
            continue;
        }

        mp.clear();
        for(int j=0;j<4;++j)//预先设定矩形四个点
        {
            mp[{dir[j][0]*A, dir[j][1]*B}]=setint, int> >();
        }
        escape=false;
        dfs(0, n);
        if(escape)puts("YES");
        else puts("NO");
    }

}

int main()
{
    solve();
    return 0;
}

/*

4 4 2
0 0 0 2 2 0
0 2 2 0 2 2
2 0 2 2 4 2
2 0 4 2 4 0

3 4 4
0 4 2 2 4 4
0 0 2 0 2 2
2 0 4 0 2 2

4 4 4
0 0 4 0 4 4
0 0 4 0 4 4
0 4 4 0 4 4
0 0 4 0 4 4
4 4 4
0 0 4 0 4 4
0 0 4 0 4 4
0 4 0 0 4 4
0 0 4 0 4 4


*/

你可能感兴趣的:(高阶算法,hihocoder,几何分析,offer收割,编程练习赛25)