EJigsaw
Problem Statement
给定 n 个物体,每个物体有 3 个横坐标,中间高度为 h,左右两边分别会有一部分突出(可能悬空,有高度)。
问是否存在一种摆放方案,使得摆成的效果,中间不能有缝隙,必须相互接触才行。下面这样的就是反例:
分析:
这道题目对我这样的CJ有好大的难度,唉~
我看了一些博主的博客才知道这是什么意思,要用什么思路去解答,奈何鄙人没做过欧拉回路的问题,都不知道还能用这个知识来解决题目。
首先要了解什么是欧拉回路,在这里我就不赘述了,直接用结论:
对于有向图来说,在连通的情况下,保证每个顶点的入度 = 出度即可
那么接下来问题来了,怎么建图呢,既然要用图论来解决,那么怎么建模就成了这题的关键。(不得不说,这个建图的思路我是看了好久才知道是怎么建的,tql)
我们规定两种点,一种称为负数点,简称N点;一种称为正数点,简称P点。
其中将边的长度量化为点,即每一个高度值代表一个点。
那么该怎么划分呢?规定当C=0时,归为N点集合,即下图的negtive2,当C!=0时,即为下图的positive1;同样的,当D=0时,归为P点集合,即为下图的positive2,反之为negtive1;(可以把这个问题想象成消消乐是不是好理解呢)。
要是可以根据题意那样相互连接,就要保证negtive1=negtive2才行。(到这步,可能有同学疑惑,下面这个图明显negtive1!=negtive2呀,确实没错,但这里的negtive1等于这个本身块的D的长度,看代码就好理解了)
l_bound=c?P(c):N(a);//c>0,则l_bound=+c,反之为-a;这里的+-代表正负,不是加减
r_bound=d?N(d):P(b);//d>0,则r_bound=-b,反之为+d;+-含义同上
Merge(l_bound,r_bound);//之所以要用到并查集,是将可以相连的连接在一起,构成一个连通图
如下图,就可以将-1,1,3连成一个整体,这就说明了这两个模块是可以连成一个整体的,当然这些连线建模都是逻辑上的,要去理解才行。
还有再说几处不太好理解的代码吧,比如下面这个
for(int i=1;i<=h+h;i++)
if(in[i] != out[i])
vis[Findf(i)]=1;
这样就把-1,3这样的边界点去掉了
还有这个
for(int i=P(1);i<=P(h);i++)
if(in[i] > out[i])
return false;
这个说的是什么意思呢?是这个意思,根据题意,我们知道要保证不能有镂空的地方,但是呢,如果模块凸出的部分都不是悬空的,那么直接连接就好,反正连接之后不会出现镂空的现象。
比如这样
因此,只要保证同等高度下,出度大于等于入度就好,反之一定不能满足题意,直接输出NO,再举一个不满足的例子
上图就是out[1]=1>in[1]=0,显然不合适
同样上图也可以解释下面的代码
out[-1]=1 < in[-1]=2
for(int i=N(1);i<=N(h);i++)
if(out[i] < in[i])
return false;
#include <iostream>
#include <cmath>
#include <algorithm>
#include <queue>
#include <vector>
#include <cmath>
#include <map>
#include <set>
#include <cstring>
#include <stack>
#include <string>
#define N(x) (x+h)
#define P(x) (x)
using namespace std;
typedef long long ll;
const int N = 1e5+199;
const double Pi = acos(-1);
int n,h;
int f[N],out[N],in[N];
int Findf(int x){
if(f[x]==x) return x;
else return f[x]=Findf(f[x]);
}
void Merge(int a,int b){
int t1=Findf(a);
int t2=Findf(b);
if(t1!=t2){
f[t2]=t1;
}
}
bool vis[N];
bool check(){
for(int i=P(1);i<=P(h);i++)
if(in[i] < out[i])
return false;
for(int i=N(1);i<=N(h);i++)
if(out[i] < in[i])
return false;
for(int i=1;i<=h+h;i++)
if(in[i] != out[i])
vis[Findf(i)]=1;
for(int i=1;i<=h+h;i++)
if(!vis[Findf(i)]&&(in[i] || out[i])) return false;
return true;
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("in.txt","r",stdin);
#endif // ONLINE_JUDGE
for(int i=0;i<=500;i++) f[i]=i;
int l_bound,r_bound;
int a,b,c,d;
scanf("%d%d",&n,&h);
for(int i=0;i<n;i++){
scanf("%d%d%d%d",&a,&b,&c,&d);
//注意这里要将负数变成正数0~h,正数变成h+1~2*h
l_bound=c?P(c):N(a);//c>0,则l_bound=+c,反之为-a;
r_bound=d?N(d):P(b);//d>0,则r_bound=-b,反之为+d
Merge(l_bound,r_bound);
//这里哪个是入度和出度没有关系,这里我令其为左边是出度,右边是入度
//若要是改成相反的,check函数的前两个for循环也要改一下
out[l_bound]++,in[r_bound]++;
}
if(check())
printf("YES\n");
else
printf("NO\n");
return 0;
}
总得来说,上面的代码解题思路好像是用排除法来做的,不知道我理解的对不对,所举得的样例都是我临时想到的,如果有不合适的地方,希望大家批评指出呀─=≡Σ(((つ•̀ω•́)つ
参考:https://www.cnblogs.com/yoyoball/p/9811158.html
https://mrsrz.github.io/AGC017E/