【写在前面】:
其实今天本来想写道水题的...然而却非常不爽
不愧是WF的出题人,居然可以卡掉各种优化的最大团算法,orz
【题目大意】:
给定平面内的n个点,选出一个点集S,使得S里的所有点两两之间欧几里得距离不超过d,问|S|的最大值以及S里的点都有哪些。若答案有多种,输出任意一个。
【题目解法】:
最大团的话还是比较显然的,然而我总是忘记最大团的剪枝,归根结底还是对剪枝本质了解不深所造成的
所以今天来补一补最大团的算法
【最大团】:(from http://www.cnblogs.com/zhj5chengfeng/archive/2013/07/29/3224092.html)
定义:一个无向图 G=(V,E),V 是点集,E 是边集。取 V 的一个子集 U,若对于 U 中任意两个点 u 和 v,有边 (u,v)∈E,那么称 U 是 G 的一个完全子图。 U 是一个团当且仅当 U 不被包含在一个更大的完全子图中。G的最大团指的是定点数最多的一个团。
解法:
1、顺序贪婪启发式搜索算法
2、局部搜索启发式算法
3、智能搜索启发式算法
4、遗传算法
5、模拟退火算法
6、禁忌算法
7、神经网络算法
8、改进蚁群算法-AntMCP
然而这些都太高端了,作为一个NP问题显然用dfs解决一切啦(雾)
【最大团的dfs解法及剪枝】
首先,给出朴素的dfs算法:
初始化:从一个点 u 开始,把这个点加入到一个集合中,设为 U。遍历一遍所有和他相连的点,把他们放入另一个集合 S1 中,接下来进行第一遍 DFS
第一遍 DFS :从 S1 中选择一个点 u1,这个点肯定和集合 U 中的任何一个点相连。把集合 S1 中 u1 能访问到的点加入到集合 S2 中,并把 u1 加入到集合 U 中,进行第二遍 DFS
第二遍 DFS :从 S2 中选择一个点 u2,这个点肯定和集合 U 中的任何一个点相连。把集合 S2 中 u2 能访问到的点加入到集合 S3 中,并把 u2 加入到集合 U 中,进行第三遍 DFS
第三遍 DFS :从 S3 中选择一个点 u3,这个点肯定和集合 U 中的任何一个点相连。把集合 S3 中 u3 能访问到的点加入到集合 S4 中,并把 u3 加入到集合 U 中,进行第四遍 DFS
......
最底层的 DFS :当某个 S 集合为空集的时候,DFS 结束,这时候我们就找到了一个完全子图,用这个完全子图更新我们的最大团。退出当前的 DFS,返回上层 DFS,接着找下一个完全子图,直到找完所有的完全子图
然而这样只是最朴素的做法,当数据规模大于20时就无能为力了...
下面是几个比较好的剪枝:
1.顺序剪枝。每次选点时按照顺序选取,即点的选择序列是单调递增的,这样可以有效避免重复状态
2.最优化剪枝。如果 U 集合中的点的数量+1(选择 ui 加入 U 集合中)+Si 中所有 ui 后面的点的数量 ≤ 当前最优值,不用再 DFS 了
3.最优化剪枝。我们用f[i]表示[i,n]区间内的点构成的最大团点数,如果 U 集合中的点的数量+1(理由同上)+[ui, n]这个区间中能构成的最大团的顶点数量 ≤ 当前最优值,不用再 DFS了
4.强大的剪枝。由于f[i]只能是f[i+1]或者f[i+1]+1(最多在[i+1,n]最大团的基础上多一个点),所以如果我们dfs到最低层时更新了答案,也就是说f[i]=f[i+1]+1了,那么此时f[i]已经最大,不可能有比f[i]更大的答案了,那么直接退出dfs
这4个剪枝可以过掉100内的数据,下面给出代码
【随机化算法】:
然而这道题的出题人很机智的卡掉了这种算法
我们考虑随机化算法。我们可以每次随机化点的序列,然后贪心选择,然后就AC了
...这样的题目有意思吗?
bzoj4080
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#define sqr(x) ((x)*(x))
using namespace std;
const int N=105;
struct point {int x,y,id;} a[N];
int n,m,b[N][N],ans,anss[N],now; bool flag;
bool v[N][N]; int f[N],c[N],tot[N];
inline long long getint()
{
long long x=0; char c=getchar(); bool flag=false;
while ((c!='-')&&((c<'0')||(c>'9'))) c=getchar();
if (c=='-') flag=true,c=getchar();
while ((c>='0')&&(c<='9')) x=x*10+(long long)(c-'0'),c=getchar();
if (flag) return -x; else return x;
}
void init()
{
n=getint(); m=getint(); m=m*m; memset(v,false,sizeof(v));
for (int i=1; i<=n; i++) a[i].x=getint(),a[i].y=getint(),a[i].id=i;
/*for (int i=1; i<=n; i++)
for (int j=1; j<=n; j++)
if (sqr(a[i].x-a[j].x)+sqr(a[i].y-a[j].y)<=m) v[i][j]=true;*/
}
/*void dfs(int x)
{
if (flag) return;
if (tot[x]==0)
{
if (x<=ans) return; ans=x; flag=true;
for (int i=1; i<=x; i++) anss[i]=c[i];
return;
}
for (int i=1; i<=tot[x]; i++)
{
if (tot[x]-i+1+x<=ans) continue;
if (f[b[x][i]]+x<=ans) continue;
c[x+1]=b[x][i]; tot[x+1]=0;
for (int j=i+1; j<=tot[x]; j++) if (v[b[x][i]][b[x][j]]) b[x+1][++tot[x+1]]=b[x][j];
dfs(x+1); if (flag) return;
}
}
void solve()
{
for (int i=n; i>=1; i--)
{
memset(tot,0,sizeof(tot)); c[1]=i;
for (int j=i+1; j<=n; j++) if (v[i][j]) tot[1]++,b[1][tot[1]]=j;
flag=false; dfs(1); f[i]=ans;
}
printf("%d\n",ans);
for (int i=1; i<=ans; i++) printf("%d ",anss[i]); printf("\n");
}*/
//上面为最大团算法
//下面为随机化算法
void solve()
{
for (int times=1; times<=1000; times++)
{
random_shuffle(a+1,a+n+1); int nowans=0;
for (int i=1; i<=n; i++)
{
bool flag=true;
for (int j=1; j<=nowans; j++)
{
if (sqr(a[i].x-a[c[j]].x)+sqr(a[i].y-a[c[j]].y)<=m) continue;
flag=false; break;
}
if (flag) c[++nowans]=i;
}
if (nowans<=ans) continue; ans=nowans;
for (int i=1; i<=ans; i++) anss[i]=a[c[i]].id;
}
printf("%d\n",ans); sort(anss+1,anss+ans+1);
for (int i=1; i<=ans; i++) printf("%d ",anss[i]); printf("\n");
}
int main()
{
init();
solve();
return 0;
}