给出两棵树,对于第一棵树的每一条边(x,y)询问有多少条在第二棵树上的边(u,v)与其交换(连接的序号相同)后两棵树依旧是一棵树。
1≤n≤2×105
先只考虑一棵树的合法情况,对于第二棵树的边(u,v)交换过来合法的当且仅当(x,y)在u→v路径上,同理的对于第二棵树合法当且仅当(u,v)在x→y路径上。
那么考虑限制一个条件,第二个条件用数据结构查询。
我们把所有的(u,v)用树上差分挂在第一棵树的u→v路径上,然后遇到一条(u,v)我们让这棵树的这条边权值+1。
这样我们就保证了处理到边(x,y)时有权值的只有在第一棵树上经过(x,y)的u→v,那么至于第二个要求我们直接在第二棵树上查询x→y路径上的权值和。这个可以用树链剖分维护。
而树上差分的合并功能就用线段树合并就好了。
时间复杂度:O(nlog2n)
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#define mp(x,y) make_pair(x,y)
using namespace std;
const int N=2e5+10,U=20;
int n,cnt,depG[N],f[N][U],rt[N],ans[N];
int siz[N],fa[N],dep[N],son[N],top[N],id[N];
vector<pair<int,int> > G[N];
vector<int> T[N],v[N];
pair<int,int> e[N];
struct SegTree{
int w[N<<5],ls[N<<5],rs[N<<5];
void Change(int &x,int L,int R,int pos,int val){
if(!x)x=++cnt;w[x]+=val;
if(L==R)return;int mid=(L+R)>>1;
if(pos<=mid)Change(ls[x],L,mid,pos,val);
else Change(rs[x],mid+1,R,pos,val);
}
int Ask(int x,int L,int R,int l,int r){
if(!x)return 0;
if(L==l&&R==r)return w[x];
int mid=(L+R)>>1;
if(r<=mid)return Ask(ls[x],L,mid,l,r);
if(l>mid)return Ask(rs[x],mid+1,R,l,r);
return Ask(ls[x],L,mid,l,mid)+Ask(rs[x],mid+1,R,mid+1,r);
}
int Merge(int x,int y,int L,int R){
if(!x||!y)return x|y;w[x]+=w[y];
if(L==R)return x;
int mid=(L+R)>>1;
ls[x]=Merge(ls[x],ls[y],L,mid);
rs[x]=Merge(rs[x],rs[y],mid+1,R);
return x;
}
}S;
void preset(int x,int fa){
f[x][0]=fa;depG[x]=depG[fa]+1;
for(int i=0;i<G[x].size();i++){
int y=G[x][i].first;
if(y==fa)continue;
preset(y,x);
}
return;
}
int LCA(int x,int y){
if(depG[x]<depG[y])swap(x,y);
for(int i=U-1;i>=0;i--)
if(depG[f[x][i]]>=depG[y])x=f[x][i];
if(x==y)return x;
for(int i=U-1;i>=0;i--)
if(f[x][i]!=f[y][i])x=f[x][i],y=f[y][i];
return f[x][0];
}
void dfs1(int x){
dep[x]=dep[fa[x]]+1;siz[x]=1;
for(int i=0;i<T[x].size();i++){
int y=T[x][i];
if(y==fa[x])continue;
fa[y]=x;dfs1(y);
siz[x]+=siz[y];
if(siz[y]>siz[son[x]])
son[x]=y;
}
return;
}
void dfs2(int x){
id[x]=++cnt;
if(son[x]){
top[son[x]]=top[x];
dfs2(son[x]);
}
for(int i=0;i<T[x].size();i++){
int y=T[x][i];
if(y==fa[x]||y==son[x])continue;
top[y]=y;dfs2(y);
}
return;
}
int GetAns(int x,int y,int rt){
int ans=0;
while(top[x]!=top[y]){
if(dep[top[x]]<dep[top[y]])swap(x,y);
ans+=S.Ask(rt,1,n,id[top[x]],id[x]);
x=fa[top[x]];
}
if(dep[x]>dep[y])swap(x,y);
if(x!=y)ans+=S.Ask(rt,1,n,id[x]+1,id[y]);
return ans;
}
void solve(int x,int fa,int ids){
for(int i=0;i<G[x].size();i++){
int y=G[x][i].first,id=G[x][i].second;
if(y==fa)continue;
solve(y,x,id);
rt[x]=S.Merge(rt[x],rt[y],1,n);
}
if(ids){
for(int i=0;i<v[x].size();i++){
int p=abs(v[x][i]);
int X=e[p].first,Y=e[p].second;
if(dep[X]>dep[Y])swap(X,Y);
S.Change(rt[x],1,n,id[Y],(v[x][i]>0)?1:-2);
}
ans[ids]=GetAns(fa,x,rt[x]);
}
return;
}
int main()
{
freopen("exchange.in","r",stdin);
freopen("exchange.out","w",stdout);
scanf("%d",&n);
for(int i=1;i<n;i++){
int x,y;
scanf("%d%d",&x,&y);
G[x].push_back(mp(y,i));
G[y].push_back(mp(x,i));
}
for(int i=1;i<n;i++){
int x,y;
scanf("%d%d",&x,&y);
T[x].push_back(y);
T[y].push_back(x);
e[i]=mp(x,y);
}
preset(1,0);
for(int j=1;j<U;j++)
for(int i=1;i<=n;i++)
f[i][j]=f[f[i][j-1]][j-1];
for(int i=1;i<n;i++){
int x=e[i].first,y=e[i].second,lca=LCA(x,y);
v[x].push_back(i);v[y].push_back(i);
v[lca].push_back(-i);
}
dfs1(1);top[1]=1;dfs2(1);
solve(1,0,0);
for(int i=1;i<n;i++)
printf("%d ",ans[i]);
return 0;
}
__EOF__
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 张高兴的大模型开发实战:(一)使用 Selenium 进行网页爬虫
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
2021-02-15 P5319-[BJOI2019]奥术神杖【0/1分数规划,AC自动机,dp】
2021-02-15 P4585-[FJOI2015]火星商店问题【线段树,可持久化Trie】
2021-02-15 P6793-[SNOI2020]字符串【广义SAM,贪心】
2021-02-15 CF803G-Periodic RMQ Problem【离散化,线段树,ST表】
2021-02-15 YbtOJ#593-木棍问题【费用流】
2021-02-15 YbtOJ#893-带权的图【高斯消元,结论】
2021-02-15 YbtOJ#631-次短路径【左偏树,最短路】