F:xay loves trees(dfs序,线段树,尺取)
题意:
给两棵树,选取一系列编号的节点,需要满足
- 在树1中节点联通,且互为祖先关系
- 在树2中节点互不为祖先关系
并且还要求这些点在第一棵树上是连续的。求最大的集合大小。
思路:
可以发现,这些点在第一颗树上必须是一条连续的链,在第二棵树上链上的点不能互相成为对方的子树结点。考虑在第一颗树上尺取,划分出一个深度最浅的结点设为上界和一个深度最深的结点设为下界。
解决方法为:将第二棵树dfs序,在第一颗树上进行dfs进行尺取,类似于滑动窗口。从根节点开始迭代,每加入一个点,需要在第二棵树上维护一个值标记此结点以及子树(用线段树维护)表示这条链不能满足,需要pop出去链的最开始的结点,每次迭代最多pop出去一个头结点,因为需要找到最长链,没有必要pop出去直到满足为止。
具体做法:
将第二棵树dfs序:将结点与子树结点成为一个连续的区间
int qian[N*2],hou[N*2];
int cnt_node;
int n;
void dfs1(int u,int fa)
{
qian[u]=++cnt_node;
for(int v:edge2[u])
{
if(v==fa)
continue;
dfs1(v,u);
}
hou[u]=++cnt_node;
}
然后将第二棵树dfs序后的结果建线段树以及一些线段树基本操作(查询,修改)
我们维护一个最大值,这样我们每加入一个点就让该点以及子树这个区间都+1,如果某个区间的最大值>=2,说明当前维护的这条链有矛盾(某个结点是某个结点的祖先)
struct tree{
int l,r;
int sum;
int mx;
int lazy;
int mid(){
return (l+r)/2;
}
}tre[N<<3];
void pushup(int rt)
{
tre[rt].mx=max(tre[rt*2].mx,tre[rt*2+1].mx);
tre[rt].sum=tre[rt*2].sum+tre[rt*2+1].sum;
}
void pushdown(int rt)
{
tre[rt*2].sum+=(tre[rt*2].r-tre[rt*2].l+1)*tre[rt].lazy;
tre[rt*2+1].sum+=(tre[rt*2+1].r-tre[rt*2+1].l+1)*tre[rt].lazy;
tre[rt*2].mx+=tre[rt].lazy;
tre[rt*2+1].mx+=tre[rt].lazy;
tre[rt*2].lazy+=tre[rt].lazy;
tre[rt*2+1].lazy+=tre[rt].lazy;
tre[rt].lazy=0;
}
void build(int rt,int l,int r)
{
tre[rt].l=l;
tre[rt].r=r;
tre[rt].lazy=0;
tre[rt].sum=0;
tre[rt].mx=0;
if(l==r)
{
tre[rt].sum=0;
tre[rt].mx=0;
return ;
}
int mid=tre[rt].mid();
build(rt*2,l,mid);
build(rt*2+1,mid+1,r);
pushup(rt);
}
void add(int rt,int l,int r,int pos)
{
if(tre[rt].l==l&&tre[rt].r==r)
{
tre[rt].sum+=(tre[rt].r-tre[rt].l+1)*pos;
tre[rt].mx+=pos;
tre[rt].lazy+=pos;
return;
}
pushdown(rt);
int mid=tre[rt].mid();
if(l>mid)
{
add(rt*2+1,l,r,pos);
}
else if(r<=mid)
{
add(rt*2,l,r,pos);
}
else
{
add(rt*2,l,mid,pos);
add(rt*2+1,mid+1,r,pos);
}
pushup(rt);
}
int query(int rt,int l,int r)
{
if(tre[rt].l==l&&tre[rt].r==r)
{
return tre[rt].mx;
}
pushdown(rt);
int mid=tre[rt].mid();
if(l>mid)
{
return query(rt*2+1,l,r);
}
else if(r<=mid)
{
return query(rt*2,l,r);
}
else
{
return max(query(rt*2,l,mid),query(rt*2+1,mid+1,r));
}
}
接下来对第一颗树进行dfs在树上进行尺取
迭代到当前点,就把这个点加入链这个集合里,同时更新线段树上的值,然后每次都要查询整个区间最值是否有>=2,如果有,说明当前链有矛盾,把链头节点pop()出去,也就是恢复区间-1,头节点右移,如果没有,更新ans取最大值,最后因为要进行迭代,要恢复原样,同时线段树也有恢复。
int ans;
int que[N*2];
int head,ed;
void dfs2(int u,int fa)
{
add(1,qian[u],hou[u],1);
que[ed++]=u;
int mx=query(1,1,2*n);
if(mx>=2)
{
add(1,qian[que[head]],hou[que[head]],-1);
head++;
}
ans=max(ans,ed-head);
for(auto v:edge1[u])
{
if(v==fa)
continue;
dfs2(v,u);
}
add(1,qian[u],hou[u],-1);
ed--;
if(mx>=2)
{
head--;
add(1,qian[que[head]],hou[que[head]],1);
}
}
结束!!
全部代码:
#include <bits/stdc++.h>
// #define int long long
using namespace std;
const int N = 3e5 + 10;
vector<int> edge1[N];
vector<int> edge2[N];
int qian[N*2],hou[N*2];
int cnt_node;
int n;
void dfs1(int u,int fa)
{
qian[u]=++cnt_node;
for(int v:edge2[u])
{
if(v==fa)
continue;
dfs1(v,u);
}
hou[u]=++cnt_node;
}
struct tree{
int l,r;
int sum;
int mx;
int lazy;
int mid(){
return (l+r)/2;
}
}tre[N<<3];
void pushup(int rt)
{
tre[rt].mx=max(tre[rt*2].mx,tre[rt*2+1].mx);
tre[rt].sum=tre[rt*2].sum+tre[rt*2+1].sum;
}
void pushdown(int rt)
{
tre[rt*2].sum+=(tre[rt*2].r-tre[rt*2].l+1)*tre[rt].lazy;
tre[rt*2+1].sum+=(tre[rt*2+1].r-tre[rt*2+1].l+1)*tre[rt].lazy;
tre[rt*2].mx+=tre[rt].lazy;
tre[rt*2+1].mx+=tre[rt].lazy;
tre[rt*2].lazy+=tre[rt].lazy;
tre[rt*2+1].lazy+=tre[rt].lazy;
tre[rt].lazy=0;
}
void build(int rt,int l,int r)
{
tre[rt].l=l;
tre[rt].r=r;
tre[rt].lazy=0;
tre[rt].sum=0;
tre[rt].mx=0;
if(l==r)
{
tre[rt].sum=0;
tre[rt].mx=0;
return ;
}
int mid=tre[rt].mid();
build(rt*2,l,mid);
build(rt*2+1,mid+1,r);
pushup(rt);
}
void add(int rt,int l,int r,int pos)
{
if(tre[rt].l==l&&tre[rt].r==r)
{
tre[rt].sum+=(tre[rt].r-tre[rt].l+1)*pos;
tre[rt].mx+=pos;
tre[rt].lazy+=pos;
return;
}
pushdown(rt);
int mid=tre[rt].mid();
if(l>mid)
{
add(rt*2+1,l,r,pos);
}
else if(r<=mid)
{
add(rt*2,l,r,pos);
}
else
{
add(rt*2,l,mid,pos);
add(rt*2+1,mid+1,r,pos);
}
pushup(rt);
}
int query(int rt,int l,int r)
{
if(tre[rt].l==l&&tre[rt].r==r)
{
return tre[rt].mx;
}
pushdown(rt);
int mid=tre[rt].mid();
if(l>mid)
{
return query(rt*2+1,l,r);
}
else if(r<=mid)
{
return query(rt*2,l,r);
}
else
{
return max(query(rt*2,l,mid),query(rt*2+1,mid+1,r));
}
}
int ans;
int que[N*2];
int head,ed;
void dfs2(int u,int fa)
{
add(1,qian[u],hou[u],1);
que[ed++]=u;
int mx=query(1,1,2*n);
if(mx>=2)
{
add(1,qian[que[head]],hou[que[head]],-1);
head++;
}
ans=max(ans,ed-head);
for(auto v:edge1[u])
{
if(v==fa)
continue;
dfs2(v,u);
}
add(1,qian[u],hou[u],-1);
ed--;
if(mx>=2)
{
head--;
add(1,qian[que[head]],hou[que[head]],1);
}
}
int main()
{
int T;
cin >> T;
while (T--)
{
cin >> n;
for (int i = 1; i <= n; i++)
{
edge1[i].clear();
edge2[i].clear();
}
cnt_node=0;
ans=1;
head=ed=0;
for (int i = 0; i < n - 1; i++)
{
int a, b;
scanf("%d%d", &a, &b);
edge1[a].push_back(b);
edge1[b].push_back(a);
}
for (int i = 0; i < n - 1; i++)
{
int a, b;
scanf("%d%d", &a, &b);
edge2[a].push_back(b);
edge2[b].push_back(a);
}
dfs1(1,0);
build(1,1,cnt_node);
dfs2(1,0);
cout<<ans<<endl;
}
}
这道题还有一个变式,如果没有连续的条件,则是原题C. Trees of Tranquillity。
关于这道题,同时也需要dfs序,线段树维护
这道题需要贪心的想,我们每次加入一个点,需要改变一个区间标记属于自己的”领地“,且不能互相”侵犯“(如果侵犯了就说明互相是祖宗了),问题就转换为怎么加入尽可能多的点,也就是改变区间就尽可能多,且不会相互覆盖(相当于互相不是祖宗)。
显然我们应该选择尽可能多的小区间(这样结点就选的多嘛),不选大区间,因为大区间覆盖的多,还不如选择多个小区间。
和上一道题不同处,我们线段树mx值应该存的是结点标号,表示这个区间以及被这个结点占领,如果有结点要覆盖它就会产生矛盾。因为dfs是从根dfs的,肯定是大区间先覆盖,小区间后出现,所以如果当前点要覆盖的区间以及有结点覆盖了,就说明当前点所覆盖的区间是小区间,原来的是大区间,更新这个区间即可。
只有dfs2和上面那道题不同,其他都一样。
代码:
#include<bits/stdc++.h>
// #define int long long
using namespace std;
const int N=3e5+10;
vector<int>edge1[N];
vector<int>edge2[N];
int cnt_node;
int qian[N*2],hou[N*2];//dfs序
void dfs1(int u,int fa)
{
qian[u]=++cnt_node;
for(int v:edge2[u])
{
if(v==fa)
continue;
dfs1(v,u);
}
hou[u]=++cnt_node;
}
int ans;
struct tree{
int l,r;
int sum;
int mx;
int lazy;
int mid(){
return (l+r)/2;
}
}tre[N<<3];
void pushup(int rt)
{
tre[rt].mx=max(tre[rt*2].mx,tre[rt*2+1].mx);
tre[rt].sum=tre[rt*2].sum+tre[rt*2+1].sum;
}
void pushdown(int rt)
{
tre[rt*2].sum+=(tre[rt*2].r-tre[rt*2].l+1)*tre[rt].lazy;
tre[rt*2+1].sum+=(tre[rt*2+1].r-tre[rt*2+1].l+1)*tre[rt].lazy;
tre[rt*2].mx+=tre[rt].lazy;
tre[rt*2+1].mx+=tre[rt].lazy;
tre[rt*2].lazy+=tre[rt].lazy;
tre[rt*2+1].lazy+=tre[rt].lazy;
tre[rt].lazy=0;
}
void build(int rt,int l,int r)
{
tre[rt].l=l;
tre[rt].r=r;
tre[rt].lazy=0;
tre[rt].sum=0;
tre[rt].mx=0;
if(l==r)
{
tre[rt].sum=0;
tre[rt].mx=0;
return ;
}
int mid=tre[rt].mid();
build(rt*2,l,mid);
build(rt*2+1,mid+1,r);
pushup(rt);
}
void add(int rt,int l,int r,int pos)
{
if(tre[rt].l==l&&tre[rt].r==r)
{
tre[rt].sum+=(tre[rt].r-tre[rt].l+1)*pos;
tre[rt].mx+=pos;
tre[rt].lazy+=pos;
return;
}
pushdown(rt);
int mid=tre[rt].mid();
if(l>mid)
{
add(rt*2+1,l,r,pos);
}
else if(r<=mid)
{
add(rt*2,l,r,pos);
}
else
{
add(rt*2,l,mid,pos);
add(rt*2+1,mid+1,r,pos);
}
pushup(rt);
}
int query(int rt,int l,int r)
{
if(tre[rt].l==l&&tre[rt].r==r)
{
return tre[rt].mx;
}
pushdown(rt);
int mid=tre[rt].mid();
if(l>mid)
{
return query(rt*2+1,l,r);
}
else if(r<=mid)
{
return query(rt*2,l,r);
}
else
{
return max(query(rt*2,l,mid),query(rt*2+1,mid+1,r));
}
}
int res;
void dfs2(int u,int fa)
{
int now=query(1,qian[u],hou[u]);
if(now==0)//说明这个区间没有
{
res++;
add(1,qian[u],hou[u],u);
}
else
{
add(1,qian[now],hou[now],-now);//删除大的区间
add(1,qian[u],hou[u],u);//添加小的区间
}
ans=max(ans,res);
for(int v:edge1[u])
{
if(v==fa)
continue;
dfs2(v,u);
}
//恢复原来的样子
if(now==0)//说明这个区间没有
{
res--;
add(1,qian[u],hou[u],-u);
}
else
{
add(1,qian[now],hou[now],now);//删除大的区间
add(1,qian[u],hou[u],-u);//添加小的区间
}
}
signed main()
{
int T;
cin>>T;
while(T--)
{
int n;
cin>>n;
for(int i=1;i<=n;i++)
{
edge1[i].clear();
edge2[i].clear();
}
for(int i=2;i<=n;i++)
{
int j;
scanf("%d",&j);
edge1[i].push_back(j);
edge1[j].push_back(i);
}
for(int i=2;i<=n;i++)
{
int j;
scanf("%d",&j);
edge2[i].push_back(j);
edge2[j].push_back(i);
}
cnt_node=0;
ans=1;
res=0;
dfs1(1,0);
build(1,1,cnt_node);
dfs2(1,0);
cout<<ans<<endl;
}
}