UOJ Logo zhouzixuan的博客

博客

bzoj 4080 [Wf2014]Sensor Network

2015-05-31 16:09:09 By zhouzixuan
【写在前面】:
    其实今天本来想写道水题的...然而却非常不爽
    不愧是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;
}

评论

zhouzixuan
@mxh1999 很抱歉,你之前看的那篇是我在隐藏blog之前没写完的版本,我也不知道为什么会发出去,这片是完整的,sorry
vfleaking
正解是 $n^4 \sqrt{n}$ 的谢谢。(常数很小 = =)
TKD
前排膜
mxh1999
前排膜

发表评论

可以用@mike来提到mike这个用户,mike会被高亮显示。如果你真的想打“@”这个字符,请用“@@”。