[题解] CF 1528C 欧拉序+stl+贪心
这是我第一次做cf div2的E题(而且还是我最不擅长的图论),内心非常激动!
题目链接
这里需要解释一下什么是团(clique):
团是图的一个子图,并且这个子图任意两个点都有一条直接边相连。
我们很容易发现,这个最大团所包含的节点,一定在第一个树的同一条树链上。
那么,我们要如何快速判断在树1的点是否在树2有子孙关系呢?
答案是欧拉序。
首先你要知道dfs序长什么样。
图片来自大佬Styx-ferryman的博客。
那么欧拉序长什么样呢?
图片来自大佬Styx-ferryman的博客。
这个玩意儿可以非常轻松地实现。
我们可以发现这个玩意儿有个非常好的性质:
如果 a a a是 b b b的祖宗节点,那么 l [ a ] < = l [ b ] & & r [ a ] > = r [ b ] l[a]<=l[b] \ \&\& \ r[a] >=r[b] l[a]<=l[b] && r[a]>=r[b]。
如果 a a a的区间和 b b b的区间相交,那么必有 a [ l , r ] ∈ b [ l , r ] a[l,r]\in b[l,r] a[l,r]∈b[l,r]或 b [ l , r ] ∈ a [ l , r ] b[l,r]\in a[l,r] b[l,r]∈a[l,r]。反之,如果不相交,那么必有 a [ l , r ] ∩ b [ l , r ] = ϕ a[l,r]\cap b[l,r]= \phi a[l,r]∩b[l,r]=ϕ。
O
(
1
)
O(1)
O(1)判断,是不是很强?!
对于树1,上面的每个点可以对应树2上的同一点,从而对应一个区间
[
l
,
r
]
[l,r]
[l,r]。
问题转化为,在所有的树链中找出一些点,使得这些点对应的区间不相交,并且点的数量最多。
我们可以边dfs树1,边用一个set维护线段。
详情看这篇文章。
需要注意的是stl自带的
l
o
w
e
r
_
b
o
u
n
d
lower\_bound
lower_bound。这玩意儿也是找第一个键值大于等于的元素地址。如果大过头了不存在则返回
.
e
n
d
(
)
.end()
.end()。
下面贴出代码和细节。
#include<bits/stdc++.h>
#define ll long long
#define ull unsigned long long
#define pii pair<int,int>
using namespace std;
const double eps = 1e-10;
const double pi = acos(-1.0);
const int maxn = 3e5 + 10;
int n;
int l[maxn],r[maxn];
vector<int> g1[maxn],g2[maxn];
int dfslock;
int cur,ans;
set<pii> st;
void init(){//初始化
for(int i = 1; i <= n; i++) g1[i].clear(), g2[i].clear();
dfslock = cur = ans = 0;
st.clear();
}
void dfs2(int u){//计算欧拉序
l[u] = ++dfslock;
for(auto& v : g2[u]) dfs2(v);
r[u] = dfslock;
}
bool having(int a, int b){//用欧拉序判断a是否是b的根节点
return l[a] <= l[b] && r[a] >= r[b];
}
void add(int x){
if(st.empty()){//集合为空,直接加
st.insert({l[x],x});
cur = 1;
return;
}
auto rg = st.lower_bound({l[x],x});
if(rg == st.begin()){//在最左边,和右不相交
if(!having(x,rg->second)) cur++;
}
else if(rg == st.end()){//在最右边,和左不相交
rg--;
if(!having(rg->second,x)) cur++;
}
else{//在中间
auto rrg = rg;//右边线段
rg--;//左边线段
if(!having(x,rrg->second)){//和右相交一定无贡献
if(!having(rg->second,x)) cur++;//和左不相交
else if(having(rg->second,rrg->second)) cur++;//和左相交,但是和右不相交,那么可以有贡献。
}
}
//插入这条链上所有节点。
st.insert({l[x],x});
}
void dfs1(int u){
add(u);
int tmp = cur;
if(g1[u].empty()) ans = max(ans,cur);//搜到叶节点,更新一下答案
for(auto& v : g1[u]){
dfs1(v);
cur = tmp;//回溯,撤销操作
}
st.erase({l[u],u});//回溯,撤销操作
}
void solve(){
scanf("%d",&n);
init();
for(int i = 2; i <= n; i++){
int x;
scanf("%d",&x);
g1[x].push_back(i);
}
for(int i = 2; i <= n; i++){
int x;
scanf("%d",&x);
g2[x].push_back(i);
}
dfs2(1);//维护树2的欧拉序
dfs1(1);//遍历树1,得出答案
printf("%d\n",ans);
}
int main()
{
int t;
scanf("%d",&t);
while(t--) solve();
return 0;
}