[WC2018]即时战略——动态点分治(替罪羊式点分树)

题目链接:

[WC2018]即时战略

题目大意:给一棵结构未知的树,初始时除1号点其他点都是黑色,1号点是白色,每次你可以询问一条起点为白色终点任意的路径,交互库会自动返回给你这条路径上与起点相邻的节点并且如果这个点为黑色则将它变为白色,要求在不多于给定次数的询问内使所有点变为白色。

大致思路为按一定顺序分别将n-1个点变为白点,为了防止被卡,需要对2~n的序列随机打乱再按打乱后的顺序逐个变白。

数据范围分为三种,分开讲解(假设当前要变白的点为x):

一、完全二叉树

这一部分比较简单,我们只需要按照一定顺序将每个点都变为白点即可。对于将每个点x变为白色的过程,因为可以确定上次询问的点一定是白点,所以我们每次询问以上次询问所返回的点为起点(第一次以根节点即1号点为起点),以x为终点询问,直到返回点为x为止。因为树高严格logn,询问次数为nlogn。

二、链

可以发现被变白的点一定是连续的一段,我们记录这一段的左右端点,每次先询问从一个端点到x的路径,如果返回点为白点说明x在1号点的另一边,再一直询问从另一个端点到x的路径并更新端点,直到x被变白为止。这样期望询问次数为n+logn(最大为2n),为了防止被卡,建议每次首先询问的端点左右交替,不要一直先询问左端点或右端点。

三、无限制

这一部分是本题的重点,题目要求的询问数上限为nlogn。可以发现白点一定组成一个联通块,对于每个待寻找的点,只要找到当前所有白点中与它相连的点即可。从完全二叉树的做法中我们可以得到启发:如果在寻找每个点的过程中只遍历到logn个节点,那么就能满足要求。而原树树高上限是O(n),无法在原树上直接寻找,但点分树保证树高为logn啊!我们每一次从点分树的根开始,假设当前走到的点为now,那么每次询问从now到x的路径,假设返回点为y,y一定是以now为分治中心时,now的一个子节点,而x一定是在y这个子树所表示的联通块中,我们只要下一次将now变为y这棵子树的分治中心再寻找就能使查找范围减小至少一半。因为时间复杂度上限不在查询上,所以对于每一次查找y这棵子树的分治中心可以直接在点分树上从y暴力往上爬,直到爬到点的父节点是now为止,这样可以减少存储的信息量(方便后边的重构)。如果对于一次询问返回点为黑点,那么需要将这个点插入到点分树中,我们直接将这个点连到当前now的下面即可。这样建出的点分树会很不平衡,我们像紫荆花之恋那道题一样设置一个平衡因子,每次插入后找到离根最近的不平衡点,对这个点在点分树上的子树进行点分治然后建出这棵子树真正的点分树,注意重构点可能是点分树的根,所以要记录点分树的根是谁。每次重构均摊O(logn^2),总时间复杂度为O(nlogn^2),询问次数为nlogn。

#include"rts.h"
#include<map>
#include<cmath>
#include<vector>
#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
int tot;
int vis[300010];
int head[300010];
int to[600010];
int next[600010];
int f[300010];
int size[300010];
int mx[300010];
int root;
int num;
int id[300010];
int col[300010];
int l,r;
int now;
int point;
int rot;
vector<int>q[300010];
inline void add(int x,int y)
{
    next[++tot]=head[x];
    head[x]=tot;
    to[tot]=y;
}
void getroot(int x,int fa)
{
    size[x]=1;
    mx[x]=0;
    for(int i=head[x];i;i=next[i])
    {
        if(!vis[to[i]]&&to[i]!=fa)
        {
            getroot(to[i],x);
            size[x]+=size[to[i]];
            mx[x]=max(mx[x],size[to[i]]);
        }
    }
    mx[x]=max(num-size[x],mx[x]);
    if(mx[x]<mx[root])
    {
        root=x;
    }
}
void dfs(int x,int fa,int rt)
{
    q[rt].push_back(x);
    size[x]=1;
    for(int i=head[x];i;i=next[i])
    {
        if(!vis[to[i]]&&to[i]!=fa)
        {
            dfs(to[i],x,rt);
            size[x]+=size[to[i]];
        }
    }
}
void partation(int x,int fa)
{
    vis[x]=1;
    f[x]=fa;
    q[x].clear();
    dfs(x,0,x);
    for(int i=head[x];i;i=next[i])
    {
        if(!vis[to[i]])
        {
            num=size[to[i]];
            root=0;
            getroot(to[i],0);
            partation(root,x);
        }
    }
}
inline void insert(int x,int fa)
{
    point=-1;
    f[x]=fa;
    vis[x]=1;
    for(int i=x;i;i=f[i])
    {
        q[i].push_back(x);
        size[i]++;
        if(f[i]&&size[i]*100>(size[f[i]]+1)*90)
        {
            point=f[i];
        }
    }
    if(point!=-1)
    {
        int len=q[point].size();
        for(int i=0;i<len;i++)
        {
            vis[q[point][i]]=0;
        }
        num=size[point];
        root=0;
        getroot(point,0);
        if(point==rot)
        {
            rot=root;
        }
        partation(root,f[point]);
    }
}
void play(int n,int T,int type)
{
    for(int i=2;i<=n;i++)
    {
        id[i]=i;
    }
    srand(12345678);
    random_shuffle(id+2,id+n+1);
    col[1]=1;
    size[1]=1;
    vis[1]=1;
    q[1].push_back(1);
    mx[0]=1<<30;
    if(type==3)
    {

        random_shuffle(id+2,id+n+1);
        l=r=1;
        for(int i=2;i<=n;i++)
        {
            if(col[id[i]])
            {
                continue;
            }
            if(col[explore(l,id[i])])
            {
                while(!col[id[i]])
                {
                    col[now=explore(r,id[i])]=1;
                    r=now;
                }
            }
            else
            {
                while(!col[id[i]])
                {
                    col[now=explore(l,id[i])]=1;
                    l=now;
                }
            }
            swap(l,r);
        }
        return ;
    }
    else
    {
        rot=1;
        for(int i=2;i<=n;i++)
        {
            now=rot;
            while(!col[id[i]])
            {
                int num=explore(now,id[i]);
                if(col[num])
                {
                    while(f[num]!=now)
                    {
                        num=f[num];
                    }
                    now=num;
                }
                else
                {
                    add(now,num);
                    add(num,now);
                    col[num]=1;
                    insert(num,now);
                    now=num;
                }
            }
        }
        return ;
    }
}

原文地址:https://www.cnblogs.com/Khada-Jhin/p/10157165.html

时间: 2024-11-06 09:30:42

[WC2018]即时战略——动态点分治(替罪羊式点分树)的相关文章

WC2018 即时战略

交互题 一棵树,一开始只有 1 号点是已知的,其他的都是未知的,你可以调用函数 explore(x,y) ,其中 x 必须是已知的,函数会找到 x 到 y 路径上第二个点,并把它标成已知,求最小步数使整棵树都已知 对于 30% 的数据,是一条链,操作次数 $O(n+logn)$ 剩下的数据,操作次数 $O(nlogn)$ $n \leq 300000$ sol: 先吐槽 loj 的交互题评测机制 把 ac 时应该输出的东西输出,然后就 a 了 不 shing 话 链的情况想了半天,题解是 xjb

动态点分治学习笔记

学习动态点分治之前要先弄清楚点分治的原理,二者的应用范围的不同就在于动态的支持在线修改操作,而实现的不同就在于动态点分治要建点分树. OI中有很多树上统计问题,这类问题往往都有一个比较容易实现的暴力做法,而用高级数据结构维护信息有显得过于复杂,有没有一种"优美的暴力",能既保证思维的简单性,又有更高效的时间复杂度保证呢?这就是点分治的思想. 点分治的实现过程是:每次找到当前树的重心,然后以这个重心为根统计这个树的信息,然后对重心的每个孩子分别递归,同样用将重心作为根的方法统计子树的信息

动态点分治入门 ZJOI2007 捉迷藏

传送门 这道题好神奇啊--如果要是不带修改的话那就是普通的点分治了,每次维护子树中距离次大值和最大值去更新. 不过这题要修改,而且还改500000次,总不能每改一次都点分治一次吧. 所以我们来认识一个新东西:带修改的点分治,动态点分治! 它可以强势解决带修改点分治问题(但是这玩意真的太难了我这个菜鸡只能学到入门) 首先我们要建立一棵树(点分树),这棵树是由点分治每次所分治的所有子树的重心串起来的.为什么要这么做呢?因为对于每次的修改,其实并没有影响到特别多的结果,它只会影响它自己所在的子树的重心

【UOJ349】【WC2018】即时战略 LCT 动态点分治

这是一道交互题 题目大意 有一棵\(n\)个点的树.最开始\(1\)号点是白的,其他点是黑的. 每次你可以执行一个操作:\(explore(x,y)\).要求\(x\)是一个白点.该函数会返回从\(x\)到\(y\)的路径上第二个点的坐标并把该点染白. 要求你把所有点都染成白色. 设操作次数为\(t\). 对于\(30\%\)的数据:这棵树是一条链(不保证\(1\)在链的一端),\(n=300000,t=O(n+\log n)\) 对于另外\(70\%\)的数据:\(n=300000,t=O(n

【BZOJ3924】幻想乡战略游戏(动态点分治)

[BZOJ3924]幻想乡战略游戏(动态点分治) 题面 权限题...(穷死我了) 洛谷 题解 考虑不修改 发现一个贪心的做法 假设当前放在当前位置 如果它有一个子树的兵的总数大于总数的一半 那么,放到那个子树的根节点上一定最优 那么,现在是动态修改 考虑动态点分治 在每个点上维护子树的兵的总数 子树到上一层父亲节点 向上走产生的贡献的总和 以及接收到子节点的贡献的总和 那么,就可以计算当前点产生的贡献 于是,从分治树根开始向下贪心即可 #include<iostream> #include&l

【WC2018】即时战略

题目描述 小M在玩一个即时战略(Real Time Strategy)游戏.不同于大多数同类游戏,这个游戏的地图是树形的. 也就是说,地图可以用一个由 n个结点,n?1条边构成的连通图来表示.这些结点被编号为 1 ~ n. 每个结点有两种可能的状态:"已知的"或"未知的".游戏开始时,只有 1号结点是已知的.在游戏的过程中,小M可以尝试探索更多的结点.具体来说,小M每次操作时需要选择一个已知的结点 x,和一个不同于 x 的任意结点 y(结点 y 可以是未知的). 然

[ZJOI2015]幻想乡战略游戏 解题报告 (动态点分治)

[ZJOI2015]幻想乡战略游戏 题意 有一棵大小为 \(n\) 的带权树, 每个点有一个权值, 权值可以修改 \(q\) 次, 找出一个补给点 \(x\) , 使得 \(\sum_{u \in V} val[u] \times dis(x,u)\) 最小, 并求出这个最小值. 一句话 : 求带权重心 (zsy说的) 附加条件 : 树中所有点的度数不超过 \(20\). 思路 一道你以为复杂度过不了, 但其实是过得了的题. 首先, 我们假定先选了一个点 \(u\) 作为补给点, 我们可以算出它

BZOJ 3924: [Zjoi2015]幻想乡战略游戏(动态点分治)

这种动态点分治嘛,GDKOI时听打到了,也有同学讲到了,所以印象比较深刻也就想出来了,然后就在实现方面卡了好久= = 不得不说CLJ说得真的太简单了,实现方面根本没提. 首先我们可以先用树分治构建出这棵树的分治树,也就是把这棵树的重心作为根节点然后子树为他的子树的重心这样递归下去,然后每个节点存的是其子树的信息. 对于每个节点我们保存这个子树的dv的总和已经把该节点作为点的答案值 这样对于修改能在log n的时间内解决 寻找答案的时候,我们可以发现,如果现在节点的子树dv和*2大于总节点,那么向

loj 2135 「ZJOI2015」幻想乡战略游戏 - 动态点分治

题目传送门 传送门 题目大意 给定一棵树,初始点权都为0,要求支持: 修改点权 询问带权重心 询问带权重心就在点分树上跑一下就行了.(枚举跳哪个子树更优) 剩下都是基础点分治. 学了一下11-dimensional的2.2k动态点分治,然后写抄出来只有1.9k??? Code /** * loj * Problem#2135 * Accepted * Time: 4492ms * Memory: 28404k */ #include <bits/stdc++.h> using namespac