题意:有一些点,距离不超过(d)的连边。求最大团。
题解:
首先,最大团是NPC的。
求最大团,可以把边取反,变为最大独立集。
如果这个图是二分图,那我们就可以做了。
这个图是二分图,说明可以把能选择点分为两部分,每部分的最远点对距离都不超过(d)。
考虑缩小可行集合使其满足二分图性质。
枚举答案的最远点对,那么,以这两个为圆心。距离为半径作圆,两个圆个公共部分是可行区域。
这个区域是“圆规”的形状,即“()”。
不难发现,在中间水平切一刀,上线两部分的最远点对都不超过(d)。
这样就变成二分图了。
时间复杂度:(O(n^2*m*sqrt{n})),即(O(n^{4.5}))。
能过。
代码:
#include <stdio.h>
#define MN 210
#define MM 2010
#define inf 99999999
int X[102],Y[102];
bool ck(int x,int y,int d)
{
return x*x+y*y<=d;
}
bool inc(int i,int j,int d)
{
return ck(X[i]-X[j],Y[i]-Y[j],d);
}
bool bk[102][102];int dd[102],co[102];
void dfs0(int u,int c,int n)
{
if(co[u])return;
co[u]=c;
for(int i=1;i<=n;i++)
{
if(bk[u][i])
dfs0(i,3-c,n);
}
}
int fr[MN],ne[MM],v[MM],w[MM],bs=0;bool bb[MN];
int dy[MN],dl[MN],od[MM],S,T,N,jl[MN];
void add(int a,int b,int c)
{
v[bs]=b;
w[bs]=od[bs]=c;
ne[bs]=fr[a];
fr[a]=bs++;
}
void addb(int a,int b,int c)
{
add(a,b,c);
add(b,a,0);
}
bool bfs()
{
for(int i=1;i<=N;i++)
{
jl[i]=inf;
bb[i]=false;
}
int he=0,ta=1;
dl[0]=S;jl[S]=0;bb[S]=true;
while(he<ta)
{
int u=dl[he];
for(int i=fr[u];i!=-1;i=ne[i])
{
if(w[i]>0&&!bb[v[i]])
{
bb[v[i]]=true;
jl[v[i]]=jl[u]+1;
dl[ta++]=v[i];
}
}
he+=1;
}
return jl[T]<inf;
}
int dfs(int u,int z)
{
if(u==T)
return z;
for(int &i=dy[u];i!=-1;i=ne[i])
{
if(w[i]>0&&jl[v[i]]==jl[u]+1)
{
int t=dfs(v[i],z<w[i]?z:w[i]);
if(t!=-1)
{
w[i]-=t;w[i^1]+=t;
return t;
}
}
}
return -1;
}
int dinic()
{
int jg=0;
while(bfs())
{
for(int i=1;i<=N;i++)
dy[i]=fr[i];
while(1)
{
int rt=dfs(S,inf);
if(rt==-1)
break;
jg+=rt;
}
}
return jg;
}
int ans[102];
int main()
{
int n,d,jg=1;
scanf("%d%d",&n,&d);
for(int i=1;i<=n;i++)
scanf("%d%d",&X[i],&Y[i]);
for(int a=1;a<=n;a++)
{
for(int b=1;b<=n;b++)
{
if(a==b)continue;
if(!inc(a,b,d*d))
continue;
int m=0,r=(X[a]-X[b])*(X[a]-X[b])+(Y[a]-Y[b])*(Y[a]-Y[b]);
for(int i=1;i<=n;i++)
{
if(i==a||i==b)continue;
if(inc(i,a,r)&&inc(i,b,r))
dd[++m]=i;
}
for(int i=1;i<=m;i++)co[i]=0;
for(int i=1;i<=m;i++)
{
for(int j=1;j<=m;j++)
bk[i][j]=!inc(dd[i],dd[j],r);
}
for(int i=1;i<=m;i++)
{
if(co[i]==0)
dfs0(i,1,m);
}
N=m+2;S=N-1;T=N;bs=0;
for(int i=1;i<=N;i++)fr[i]=-1;
for(int i=1;i<=m;i++)
{
if(co[i]==1)
{
addb(S,i,1);
for(int j=1;j<=m;j++)
{
if(bk[i][j])
addb(i,j,inf);
}
}
else addb(i,T,1);
}
int rt=m-dinic()+2;
if(rt>jg)
{
int s=0;
for(int i=1;i<=m;i++)
{
if(co[i]==1&&jl[i]!=inf)
ans[s++]=dd[i];
else if(co[i]==2&&jl[i]==inf)
ans[s++]=dd[i];
}
ans[s++]=a;ans[s++]=b;
jg=rt;
}
}
}
printf("%d
",jg);
if(jg==1)ans[0]=1;
for(int i=0;i<jg;i++)
printf("%d ",ans[i]);
return 0;
}
此外,模拟退火也能过。