题目描述
输入描述:
输出描述:
示例1
输入
6
5 20
1 2
1 3
1 4
1 5
3 8
输出
Yes
1 1
2 2
3 3
4 4
5 5
No
No
Yes
1 1
No
Yes
1 1
1 2
2 1
说明
题目大意
在一个无限平面上,所有的点是白色的,现在你可以将平面上任意n个点染黑。此时定义任意相邻的两个点如果颜色不同,则称其为一个点对。现要求你找到n个点,使得它们染黑之后恰好有m个点对。若无解,则输出"No"。否则输出"Yes",并输出你选择的点的坐标。(SPJ)
分析
首先来看无解的情况。
无解
先可以求出n个点的能形成的点对的数量的上下限。
上限:很容易可以想到如果两个黑点靠在一起,肯定会使得点对变少。因此n个点最多形成的点对就是互相不相邻时,有n*4个点对。
下限:运用小学二年级的等周定理,轻易得出,当一个矩形周长一定时,正方形的面积最大。逆推,可知面积一定时,正方形的周长最小。因此,当n个点都挤成一坨时(近似正方形),理论最小值为4*sqrt(n)。
其他无解:由于一个点时有4个点对,每增加一个点,就会多出2或4或0个点对,因此不可能是奇数个。所以当m为奇数时也无解。
有解
终于轮到有解情况了。首先我们可以把这些点都堆到一起,使得点对的数量最小。然后考虑将这些点“放逐”掉,即使这些点到空旷的地方使得它可以贡献4个点对。
此时,我们按顺序放逐点。从最后一个开始,如果有2个点与待放逐点相邻,则这个点放逐后多出4个点对;如果只有一个,则这个点放逐后多2个点对。(这里自己画图试下就可以了)
基于此,可以把点对的数量增加到m。但是我们这个算法,大概率每次加4,万一过头了,这就需要特赦令,将原来放逐的点收回。那么收回到哪里呢?显然,过头时只会比m多2,因此我们只要将点对少2即可。通过之前的经验,只要把这个点放到一个位置,使得只有一个点与之相邻即可。
后文代码中是将点全部放逐到第三象限去了,原矩阵是在第一象限。
WAWA点
不止最后一行,最后一列放逐时也只有一个点相邻,只能+2。
循环到st时要注意多出来的不止一行。
注意换行!!!
(结合后文代码体会蒟蒻的艰苦WAWA之路看易错点)
代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
int x[60],y[60];
int main()
{
int t,n,m;
for(scanf("%d",&t);t--;){
scanf("%d%d",&n,&m);
if((m&1)||m>4*n||m*m<16*n){puts("No");continue;}//判断无解
puts("Yes");
int num=0,sum=0,st=sqrt(n),i,j,trash=0;//num 未放逐的点数量 sum 点对的数量 trash 垃圾,即被放逐的点数量
for(i=1;i<=st;i++)
for(j=1;j<=st;j++)
num++,x[num]=i,y[num]=j;//先搞成正方形
sum=st*4;//此时点对为st*4,st是边长
for(i;num<n;i++){
sum+=2;//相较原来,每多一行,就多2个点对
for(j=1;num<n&&j<=st;j++)//这里的j<=st是上面WAWA点的第三条
num++,x[num]=i,y[num]=j;
}//注意不止一行,也是WAWA点的第二条
while(sum<m) sum+=((y[num]==1||x[num]==1)?2:4),num--,trash++;//WAWA点第一条,sum的值在x[num]==1时也要+2
//哦豁去世
if(sum>m){trash--,num++,x[num]=0,y[num]=1;}//过头了
for(int k=1;k<=num;k++) printf("%d %d\n",x[k],y[k]);
for(int k=1,xx=-1,yy=-1;k<=trash;k++,xx--,yy--)
printf("%d %d\n",xx,yy);//放逐点直接扔第三象限去
}
}
END
有错即评。