题解 P2546 【[POI2008]SZK - Mirror trap】
SuperJvRuo · · 题解
第一问
物理题,一个点射出的光线只能射到一个点上,由于光路可逆,所以反过来的点也只有一个。因此答案为
第二问
模拟题,可以模拟出所有的光线得到匹配。先走一遍路记录边上所有的整点坐标。将图逆时针旋转
最后在图上走路即可。
#include <cstdlib>
#include <cstdio>
#include <cmath>
#include <algorithm>
const int MAX=1e6;
struct point
{
int x,y,ver,num;
};
bool cmp1(point a,point b)
{
return a.x!=b.x?a.x<b.x:a.y<b.y;//按旋转后的横坐标排序
}
bool cmp2(point a,point b)
{
return a.y!=b.y?a.y<b.y:a.x<b.x;//按旋转后的纵坐标排序
}
int len[MAX];
int next[MAX][2];
point side[MAX];//边上的点
int n,m,akt,a,b,c,p;
void make_next(int wh)
{
std::sort(side,side+n,wh?cmp2:cmp1);
akt=(-1);
for(int i=0;i<n;++i)
{
if ((side[i].ver>=0)&&((side[i].ver&1)!=wh))
//发现这个点是顶点 凸的方向与扫的方向不同
{
next[side[i].num][wh]=(-side[i].ver/2-1);//便于输出的一个处理
continue;//跳过
}
if (akt<0)
{
akt=i;
}
else
{ //可以射过光线,配对
next[side[akt].num][wh]=side[i].num;
next[side[i].num][wh]=side[akt].num;
akt=(-1);
}
}
}
void insert()
{
side[n].x=a-b;//相当于把图形逆时针旋转了45°
side[n].y=a+b;
side[n].ver=(-1);
side[n].num=n;
n++;
}
void find(int fr)
{
p=fr;
akt=((next[fr][0]>=0)?0:1);
while(next[p][akt]>=0)//直到找到一个顶点
{
p=next[p][akt];
akt^=1;//横竖交替走路
}
p=(next[p][akt]*=(-1));
}
int pp(int k)
{
return k>=0?k:(m-1);
}
int main()
{
scanf("%d",&m);
printf("%d\n",m>>1);
for(int i=0;i<m;++i)
{
scanf("%d %d",&a,&b);
len[i]=a+b;
}
c=len[m-1];
for(int i=m-1;i>=1;--i)
{
len[i]-=len[i-1];
}
len[0]-=c;//预处理每条边的长度和方向
a=0;
b=0;
for(int i=0;i<m;++i)//走路
{
if (i%2)//横向的边
{
if (len[i]>0)//往正方向走路
{
for(int j=0;j<len[i];++j)
{
insert();//把点加进去
a++;
}
side[n-len[i]].ver=((len[pp(i-1)]>0)?(2*i+1):(2*i));
//根据走路方向加上这个1,表示旋转后这个点向左或右凸出
//否则即为向上或下凸
}
else//往负方向走路
{
for(int j=0;j<-len[i];++j)
{
insert();
a--;
}
side[n+len[i]].ver=((len[pp(i-1)]<0)?(2*i+1):(2*i));
}
}
else//纵向的边
{
if (len[i]>0)//往正方向走路
{
for(int j=0;j<len[i];++j)
{
insert();
b++;
}
side[n-len[i]].ver=((len[pp(i-1)]>0)?(2*i+1):(2*i));
}
else//往负方向走路
{
for(int j=0;j<-len[i];++j)
{
insert();
b--;
}
side[n+len[i]].ver=((len[pp(i-1)]<0)?(2*i+1):(2*i));
}
}
}
//连光线
make_next(0);
make_next(1);
for(int i=0;i<n;++i)
{
if ((next[i][0]<0)||(next[i][1]<0))//有一个小于0,是顶点
{
find(i);//找到和它唯一对应的顶点
printf("%d %d\n",pp(((next[i][0]<0)?(next[i][0]*(-1)):(next[i][1]*(-1)))-2)+1,pp(p-2)+1);
}
}
return 0;
}