[loj6736]最小连通块

定义$f(S)$表示点集$S$的最小连通块


做法1

通过对所有节点判定,可以在$n$次询问中求出具体的$f(S)$

对于$x\ne y$,显然$(x,y)\in E$当且仅当$f(\{x,y\})=\{x,y\}$,那么直接暴力判定即可

询问次数和时间复杂度均为$o(n^{3})$


做法2

为了方便,以下认为原树为有根树,且以1为根

对于$x\ne y$,显然$x$为$y$的祖先当且仅当$x\in f(\{1,y\})$,同样直接暴力判定即可

得到任意两点祖先后代关系后,再拓扑排序即可得到整棵树

询问次数和时间复杂度均为$o(n^{2})$


做法3

将上述结论拓展,即对于$x\not\in S$,$x\in f(\{1\}\cup S)$当且仅当$S$中存在$x$的后代

若满足此条件,则可以进行以下两种操作——

在$S$中二分,可以在$o(\log |S|)$次询问和$o(|S|)$的复杂度内找到某个$S$中$x$的后代

在$S$中搜索,即重复上述二分并在当前$S$中不存在$x$的后代时结束,可以在$o(m\log |S|)$次询问和$o(m|S|\})$的复杂度内找到$S$中所有$x$的后代(其中$m$为$S$中$x$的后代数)

下面,如果将原树看作有向树(方向为儿子到父亲),并得到了其任意一个拓扑序,那么即可利用上述做法得到整棵树,具体做法如下:

维护当前点集$S$(初始为$V$),并从前往后枚举拓扑序上的点$x$,并在$S$中找到$x$的儿子并删除,不难发现此时$S$中所有$x$的后代必然都是$x$的儿子,因此通过前面的搜索来实现

注意到每一个点恰被计入$m$一次,因此询问次数约为$o(n\log n)$,时间复杂度为$o(n^{2})$

显然这个复杂度是可以接受的,那么以下问题即变为如何求拓扑序


做法4

考虑拓扑序,实际上即不断找到原树的一个叶子,而$x$为叶子当且仅当$x\not\in f(V-\{x\})$

由此,可以在$n$次询问和$o(n^{2})$的复杂度内找到一个叶子,重复此操作即可(注意$V$要删除得到的叶子)

询问次数为$o(n^{2})$,时间复杂度为$o(n^{3})$


做法5

根据上述分析,实际上是需要优化找叶子的复杂度

考虑这样一个构造:从根节点开始,不断随机找到一个后代并变为该后代

记当前节点为$x$,选出$x$子树中所有子树大小$>\frac{sz_{x}}{2}$的点,假设有$s$个(不包括$x$),那么这些节点中最深的一个子树中至多有$sz_{x}-s$个点,也即得到$s<\frac{sz_{x}}{2}$,由于$s$为整数也可以看作$s\le \frac{sz_{x}-1}{2}$

换言之,每一次递归有$\frac{1}{2}$的概率子树大小除以2,那么期望两步使得子树大小除以2,因此子树大小从$n$变为1(叶子)的期望步数即$2\log n$(实际可能略少)

关于如何找到某个后代,(若$x$不为叶子)$V-\{x\}$中总存在$x$的后代,再使用之前的二分即可

询问次数为$o(n\log^{2}n)$,时间复杂度为$o(n^{2}\log n)$


做法6

换一个思路,去减少找叶子的轮数,即每一次取出所有叶子一起处理

但这样显然会被一条链卡掉,那么再对于每一个叶子向上找一条链,满足链上的点(目前)都仅有一个儿子(注意节点会被删除),将这条链上的点也删除

令$T(x)$为$x$被删除时的轮数(初始叶子有$T(x)=1$),根据上述过程,当$T(son)$中最大值不唯一时$T(x)$即为$T(son)$的最大值,否则$T(x)$为$T(son)$最大值+1

归纳证明$T(x)\ge n$的$x$满足$sz_{x}\ge 2^{n}-1$,根据转移显然成立,因此轮数即为$\log n$

下面,问题即如何找到这样的链:

首先,仍然按照做法4,找出所有叶子,假设其构成集合$L$

一个点在链上,当且仅当其恰有一个后代在$L$中,这可以先二分找到$L$中的某个后代,再判定剩下的部分中是否还存在后代,进而每一个后代对应的点即构成其向上的链

为了保证这些点的拓扑序,再将其sort一下即可

询问次数为$o(n\log^{2}n)$,时间复杂度为$o(n^{2}\log n)$


做法7

不再去找叶子,而是考虑分治——

构造函数$solve(S,x)$,保证$S$是$x$子树中若干个点(包含$x$),其需要求出$S$的一个拓扑序

若$S=\{x\}$显然拓扑序即仅含$x$,否则考虑分治:任取$S-\{x\}$中的一个元素$y$,并找出$S$中是$y$后代的元素,然后将该部分从$S$中取出分别求拓扑序

对$S-\{y\}$中的点染色,两点同色当且仅当删除$y$后两点在同一个连通块中,并称$x$的颜色为"白色",其余颜色为"彩色",显然判定$S'\subseteq S$中的点是否同色即等价于$y\not\in f(S')$

进一步的,将$S$中的点排在序列上,并利用上述操作不断找到下一个极长连续同色段,那么即有段数次二分

(注意只需要区分白色和彩色,而不需要区分具体的颜色)

假设颜色相同的点最多有$m$个,注意到剩下的点只有$|S|-m$个,因此不难发现至多只有$o(|S|-m)$段

换言之,可以看作每一次不属于这$m$个点的点都有一次二分的贡献

考虑每一个点的贡献,对该点颜色分类讨论:

1.为白色点,注意到该次分治$S$必然缩小一半,至多$\log n$次

2.为彩色点,再分类讨论:

(1)这$m$个点为白色,同样$S$必然缩小一半,至多$\log n$次

(2)这$m$个点为彩色(且不为该点的颜色),那么其必然是$y$的轻儿子,而每一个$y$只被以此法访问一次且每一个点到根路径上只有$\log n$条轻边,因此同样至多$\log n$次

综上,至多$o(n\log n)$次二分

询问次数为$o(n\log^{2}n)$,时间复杂度为$o(n^{2}\log n)$


做法8

实际上,并不需要真的划分出$y$的子树,不妨直接递归

具体的,重新构造函数$solve(S,x)$,其需要求出$S$与$x$子树交的一个拓扑序(保证$x\in S$)

若$S-\{x\}$中不存在$x$的后代,那么该拓扑序即仅包含$x$,直接返回即可

否则,二分找到$S-\{x\}$中$x$的一个后代$y$,然后执行$solve(S,y)$得到拓扑序,再将该拓扑序中的点(即$S$与$y$子树交的点)在$S$中删除,重复此过程即可

由此执行$solve(V,1)$即可,注意到每一个节点至多执行一次二分,因此共计即$n$次二分

询问次数为$o(n\log n)$,时间复杂度为$o(n^{2})$

 1 #include<bits/stdc++.h>
 2 #include"C.hpp"
 3 using namespace std;
 4 #define N 1005
 5 #define pii pair<int,int>
 6 #define vi vector<int>
 7 int n;
 8 vi V,S0,Pos;
 9 vector<pii>E;
10 void del(vi &v,int x){
11     for(int i=0;i<v.size();i++)
12         if (v[i]==x){
13             v.erase(v.begin()+i);
14             break;
15         }
16 }
17 int get_one(vi S,int x){
18     del(S,1);
19     if (x==1){
20         if (S.size())return S[0];
21         return 0;
22     }
23     S0=S,S0.push_back(1);
24     if (!ask(S0,x))return 0;
25     int l=0,r=S.size()-1;
26     while (l<r){
27         int mid=(l+r>>1);
28         S0.clear(),S0.push_back(1);
29         for(int i=l;i<=mid;i++)S0.push_back(S[i]);
30         if (ask(S0,x))r=mid;
31         else l=mid+1;
32     }
33     return S[l];
34 }
35 vi get_all(vi S,int x){
36     Pos.clear();
37     while (1){
38         int pos=get_one(S,x);
39         if (!pos)break;
40         Pos.push_back(pos);
41         del(S,pos);
42     }
43     return Pos;
44 }
45 vi calc(vi S,int x){
46     vi ans(0);
47     while (1){
48         del(S,x);
49         int y=get_one(S,x);
50         if (!y){
51             ans.push_back(x);
52             break;
53         }
54         vi s=calc(S,y);
55         for(int i=0;i<s.size();i++){
56             ans.push_back(s[i]);
57             del(S,s[i]);
58         }
59     }
60     return ans;
61 }
62 vector<pii> get_E(vi order){
63     V.clear(),E.clear();
64     for(int i=1;i<=n;i++)V.push_back(i);
65     for(int i=0;i<n;i++){
66         del(V,order[i]);
67         vi son=get_all(V,order[i]);
68         V.push_back(order[i]);
69         for(int j=0;j<son.size();j++){
70             E.push_back(make_pair(order[i],son[j]));
71             del(V,son[j]);
72         }
73     }
74     return E;
75 }
76 vector<pii> work(int nn){
77     n=nn;
78     for(int i=1;i<=n;i++)V.push_back(i);
79     return get_E(calc(V,1));
80 }
View Code

做法9

再换一个思路,直接从拓扑序上考虑

具体的,拓扑序$\{a_{i}\}$的限制即不存在$1\le x<y\le n$,使得$a_{x}$是$a_{y}$的祖先

注意到这类似于逆序对,即联想到排序,那么初始令$a_{i}=i$,并执行插入排序

换言之,即需要对于已有的拓扑序$\{a_{1},a_{2},...,a_{k}\}$,加入一个新的$x$并保证拓扑序的性质

根据之前的性质,找到最长的后缀使得其中没有$x$的后代,注意到若$x$不为第一个元素,那么$x$的上一个元素必然恰好是$x$的后代,因此更之前的元素中不存在$x$的祖先(否则与其本来为拓扑序矛盾),因此所有与$x$相关的点对仍满足上述性质,即仍为拓扑序

显然该过程可以二分,同样也仅包含$n$次二分

询问次数为$o(n\log n)$,时间复杂度为$o(n^{2})$

 1 #include<bits/stdc++.h>
 2 #include"C.hpp"
 3 using namespace std;
 4 #define N 1005
 5 #define pii pair<int,int>
 6 #define vi vector<int>
 7 int n;
 8 vi V,S0,Pos,order;
 9 vector<pii>E;
10 void del(vi &v,int x){
11     for(int i=0;i<v.size();i++)
12         if (v[i]==x){
13             v.erase(v.begin()+i);
14             break;
15         }
16 }
17 int get_one(vi S,int x){
18     S0=S,S0.push_back(1);
19     if (!ask(S0,x))return -1;
20     int l=0,r=S.size()-1;
21     while (l<r){
22         int mid=(l+r>>1);
23         S0.clear(),S0.push_back(1);
24         for(int i=mid+1;i<=r;i++)S0.push_back(S[i]);
25         if (ask(S0,x))l=mid+1;
26         else r=mid;
27     }
28     return l;
29 }
30 vi get_all(vi S,int x){
31     Pos.clear();
32     del(S,1);
33     if (x==1)return S;
34     while (1){
35         int pos=get_one(S,x);
36         if (pos<0)break;
37         Pos.push_back(S[pos]);
38         del(S,S[pos]);
39     }
40     return Pos;
41 }
42 vector<pii> get_E(vi order){
43     V.clear(),E.clear();
44     for(int i=1;i<=n;i++)V.push_back(i);
45     for(int i=0;i<n;i++){
46         del(V,order[i]);
47         vi son=get_all(V,order[i]);
48         V.push_back(order[i]);
49         for(int j=0;j<son.size();j++){
50             E.push_back(make_pair(order[i],son[j]));
51             del(V,son[j]);
52         }
53     }
54     return E;
55 }
56 vector<pii> work(int nn){
57     n=nn;
58     for(int i=2;i<=n;i++){
59         int pos=get_one(order,i)+1;
60         order.insert(order.begin()+pos,i);
61     }
62     order.push_back(1);
63     return get_E(order);
64 }
View Code

 

posted @ 2021-09-24 17:56  PYWBKTDA  阅读(253)  评论(0编辑  收藏  举报