snoi模拟赛 day4t2 最大团

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);
	}
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值