【学习笔记】「JOISC 2017 Day 3」自然公园

考虑对于一个点 x x x,找到它与当前连通块相连的所有边。我们希望通过 log ⁡ n \log n logn次询问确定一条边。

一般的策略是将数划分成两个大小相同集合然后去找答案,这里考虑 二分前缀,因为将点按照 D F N DFN DFN序排序,可以保证这段前缀构成一个连通块。那么固定根节点 r r r,询问 x x x r r r是否可达,找到最小的前缀 y y y,就能确定 x x x y y y之间有一条边。将 y y y删掉后处理剩下的连通块即可,这部分有 m log ⁡ n m\log n mlogn次。

然后考虑怎么确定 x x x。发现可以通过类似的方法确定从 x x x到连通块的路径上的一个点 y y y,那么先递归处理 y y y,直到不存在这样的 y y y时再把 x x x加入连通块即可。注意到每个节点只会被找到一次,这部分复杂度 n log ⁡ n n\log n nlogn

总次数 ( n + m ) log ⁡ n (n+m)\log n (n+m)logn

#include<bits/stdc++.h>
#include "park.h"
#define fi first
#define se second
#define pb push_back
#define ll long long
using namespace std;
mt19937 gen(114514);
int vs[1405],vs2[1405],state[1405],n;
vector<int>nums;
vector<int>G[1405];
void dfs(int u,int topf){
    nums.pb(u);
    for(auto v:G[u]){
        if(v==topf||vs2[v])continue;
        dfs(v,u);
    }
}
int work(int p,int rt){
    nums.clear(),dfs(rt,-1);
    memset(vs,0,sizeof vs);
    for(auto e:nums)vs[e]=1;vs[p]=1;
    if(Ask(min(p,rt),max(p,rt),vs)==0)return -1;
    int l=1,r=nums.size(),res=0;
    while(l<=r){
        int mid=l+r>>1;
        memset(vs,0,sizeof vs);
        for(int i=0;i<mid;i++)vs[nums[i]]=1;vs[p]=1;
        if(Ask(min(p,rt),max(p,rt),vs))res=mid,r=mid-1;
        else l=mid+1;
    }
    int tmp=nums[res-1];vs2[tmp]=1;
    Answer(min(p,tmp),max(p,tmp));
    for(auto u:G[tmp]){
        if(vs2[u]==0)work(p,u);
    }return tmp;
}
int get(int x){
    int l=1,r=n,res=0;
    while(l<=r){
        int mid=l+r>>1;
        for(int i=0;i<mid;i++){
            vs[i]=(state[i]==1||state[i]==0);
        }
        for(int i=mid;i<n;i++){
            vs[i]=(state[i]==1);
        }
        vs[x]=1;
        if(Ask(0,x,vs)){
            res=mid,r=mid-1;
        }
        else{
            l=mid+1;
        }
    }return res-1;
}
void solve(int x){
    state[x]=2;int y;
    while(1){
        memset(vs2,0,sizeof vs2);
        y=work(x,0);
        if(~y){
            state[x]=1;
            G[x].pb(y),G[y].pb(x);
            return;
        }
        solve(get(x));
    }
}
void Detect(int T,int N){
    n=N;
    state[0]=1;
    for(int i=1;i<n;i++){
        if(state[i]==0)solve(i);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值