Description
给定二维坐标上的N个点,如果两个点之间的距离大于K,则他们不能同时被选取。
求最大团的大小。也即,选出最多点,使得这些点两两之间的距离不大于K。
这个题正解太巧妙了!
首先我们可以枚举两个点i,j,如果这两个点合法,将它们两个之间的距离定为基准,即最长值。
那么我们就可以继续枚举其他的点k,如果这个点的距离到两个端点的距离都小于等于上面的基准值,那么我们可以进行以下的操作,
如果k与i的斜率大于j与i的斜率,则将它放到左集合,否则放到右集合,如下图,左集合即黄色,右集合即橙色。
那么我们便可以得到一个有意思的性质,左右集合内部自己的点,距离一定小于等于两个点i,j的距离,默认是有边的,而它的补图则是没有边的,那么我们只需要建出它的补图,由一个图的最大匹配数=总点数-补图的二分图最大匹配数,再加上端点的两个点即可。
为什么这样求出来的一定是最大团呢,因为对于最大团,整个团距离的最大的两个点,是一定可以包含其他点的,不然会有距离更大的点来包含。
下附AC代码
#include<iostream>
#include<stdio.h>
#include<string.h>
#include<algorithm>
#define maxn 305
using namespace std;
int n,d;
int x[maxn],y[maxn];
int edge[maxn][maxn];
int cntl,cntr,l[maxn],r[maxn],vis[maxn],match[maxn];
int getdis(int i,int j)
{
return (x[i]-x[j])*(x[i]-x[j])+(y[i]-y[j])*(y[i]-y[j]);
}
int getup(int i,int j,int k)
{
int x1=x[k]-x[i],x2=x[j]-x[i];
int y1=y[k]-y[i],y2=y[j]-y[i];
return x1*y2-x2*y1;
}
int dfs(int now)
{
for(int i=1;i<=cntr;i++)
if(edge[now][i] && !vis[i])
{
vis[i]=1;
if(!match[i] || dfs(match[i]))
{
match[i]=now;
return true;
}
}
return false;
}
int hungry()
{
int cnt=0;
for(int i=1;i<=cntr;i++) match[i]=0;
for(int i=1;i<=cntl;i++)
{
for(int j=1;j<=cntr;j++) vis[j]=0;
if(dfs(i)) cnt++;
}
return cnt;
}
int main()
{
int _;
scanf("%d",&_);
while(_--)
{
int ans=-1;
scanf("%d%d",&n,&d);
for(int i=1;i<=n;i++)
scanf("%d%d",&x[i],&y[i]);
for(int i=1;i<=n;i++)
{
for(int j=i+1;j<=n;j++)
{
if(getdis(i,j)<=d*d)
{
cntl=0;cntr=0;
int temp=getdis(i,j);
for(int k=1;k<=n;k++)
if(k!=i && k!=j && getdis(i,k)<=temp && getdis(j,k)<=temp)
{
if(getup(i,j,k)<=0) l[++cntl]=k;
else r[++cntr]=k;
}
for(int k=1;k<=cntl;k++)
for(int o=1;o<=cntr;o++)
edge[k][o]=0;
for(int k=1;k<=cntl;k++)
for(int o=1;o<=cntr;o++)
if(getdis(l[k],r[o])>d*d)
edge[k][o]=1;
int now=cntl+cntr-hungry();
ans=max(ans,now);
}
}
}
printf("%d\n",ans+2);
}
}