树上问题——dfs序
题目描述
Adera是Microsoft应用商店中的一款解迷游戏。
异象石是进入Adera中异时空的引导物,在Adera的异时空中有一张地图。这张地图上有N个点,有N-1条双向边把它们连通起来。起初地图上没有任何异象石,接下来的M个 时刻中,每个时刻会发生以下三种类型的事件之一:
1、地图的某个点上出现了异象石(已经出现的不会再次出现);
2、地图某个点上的异象石被摧毁(不会摧毁没有异象石的点);
3、向玩家询问使所有异象石所在的点连通的边集的总长度最小是多少。
请你作为玩家回答这些问题。
输入格式
第一行有一个整数N,表示点的个数。
接下来N-1行,每行三个整数x,y,z,表示点x和y之间有一条长度为z的双向边。
第N+1行有一个正整数M。
接下来M行每行是一个事件,事件是以下三种格式之一:
+x 表示点x上出现了异象石
-x 表示点x上的异象石被摧毁
? 表示询问使当前所有异象石所在的点连通所需的边集的总长度最小是多少。
输出格式
对每个?事件,输出一个整数表示答案。
样例输入
6
1 2 1
1 3 5
4 1 7
4 5 3
6 4 2
10
+ 3
+ 1
?
+ 6
?
+ 5
?
- 6
- 3
?
样例输出
5
14
17
10
数据范围与约定
对于30%的数据,1<=n,m<=1000。
对于另20%的数据,地图是一条链,或者一朵菊花。
对于100%的数据,1<=n,m<=10^5,1<=x,y<=n,x!=y,1<=z<=10^9
force:
#include<cstdio> #include<algorithm> #include<cstring> #include<cstdlib> #include<iostream> #include<set> #define LL long long using namespace std; const int maxn=1e5+5; inline int read(){ int a=0;bool b=1;char x=getchar(); while(x<'0'||'9'<x){ if(x=='-')b=0; x=getchar(); } while('0'<=x&&x<='9'){ a=(a<<3)+(a<<1)+x-'0'; x=getchar(); } return b ? a : -a ; } int indegree[maxn]; int first[maxn],next[maxn*2],to[maxn*2],w[maxn*2],edge_count; inline void add(int x,int y,int z){ edge_count++; to[edge_count]=y; w[edge_count]=z; next[edge_count]=first[x]; first[x]=edge_count; indegree[y]++;//统计入度 } int m,n,sp_one[maxn]; char ch[5]; inline void special_one(int root){ for(int i=first[root];i;i=next[i]){ sp_one[ to[i] ]=w[i]; // printf("sp%d %d\n",to[i],sp_one[ to[i] ]); } LL ans=0ll,size=0ll; m=read(); for(int i=1;i<=m;i++){ scanf("%s",ch+1); // puts(ch+1); switch(ch[1]){ case'+':{ size++; int u=read(); ans+=(LL)sp_one[u]; break; } case'-':{ size--; int u=read(); ans-=(LL)sp_one[u]; break; } case'?':{ printf("%lld\n",size<=1?0ll:ans); break; } } } } /*int num[maxn],T;//tree to link bool vis[maxn];*/ LL dis[maxn]; int T,seg[maxn]; void dfs_num(int u,int fa){ seg[u]=++T; for(int i=first[u];i;i=next[i]){ int v=to[i]; if(v==fa)continue; dis[T+1]=dis[T]+w[i]; // printf("%d %lld\n",T,dis[T]); dfs_num(v,u); } } /*int fa[maxn][2]; inline int find(int x,int b){ if(fa[x][b]==x)return x; else return fa[x][b]=find(fa[x][b]); }*/ inline void special_two(int root){ dfs_num(root,0); /*for(int i=1;i<=n;i++){ fa[i][0]=fa[i][1]=i; }*/ set<int>s; m=read(); // LL ans=0,size=0; for(int i=1;i<=m;i++){ scanf("%s",ch+1); switch(ch[1]){ case'+':{ int u=read(); s.insert(seg[u]); break; } case'-':{ // size--; int u=read(); // ans-=(LL)sp_one[u]; s.erase(seg[u]); break; } case'?':{ if(s.size()){ set<int >::iterator it=s.end();it--; printf("%lld\n",dis[*it]-dis[*s.begin()]); } else printf("0\n"); break; } } } } bool vis[maxn]; int cnt[maxn],all; //cnt[i]表示节点i的子树有几个被标记的点 int search(int u,int fa){ cnt[u]=vis[u]; int ans=0; for(int i=first[u];i;i=next[i]){ int v=to[i]; if(v==fa)continue; ans+=search(v,u); if(0<cnt[v] && cnt[v]<all)ans+=w[i]; cnt[u]+=cnt[v]; } return ans; } int main(){ n=read(); for(int i=1;i<n;i++){ int x=read(),y=read(),z=read(); add(x,y,z);add(y,x,z); } int start=0,temp1=0,temp2=0;//入度为1 || 2的点 for(int i=1;i<=n;i++){ if(indegree[i]==n-1){special_one(i);exit(0);} if(indegree[i]==2)temp2++; if(indegree[i]==1)temp1++,start=i; } if(start && temp1==2 && temp2==n-2){ special_two(start);exit(0); } m=read(); int size=0; for(int i=1;i<=m;i++){ scanf("%s",ch+1); switch(ch[1]){ case'+':{ int u=read(); vis[u]=1; size++; break; } case'-':{ int u=read(); vis[u]=0; size--; break; } case'?':{ if(size>1){ all=size; printf("%d\n",search(1,0)); } else printf("0\n"); break; } } } return 0; }
平衡树(set)+LCA:
这道题我们发现实际上每次插入or删除一个点的时候
都相当于与寻找这个点的最近的被标记的点,
这类问题,如果是在链上就很好解决,平衡树处理前驱后继
所以我们想树转链——dfs序
还有一个问提:如何保证每次增加的路径都是最短的,而不会跨过某个多余的点?
我们可以维护二倍的路径,最终/2得到结果。
,每次只需要找到节点的前驱和后继,和前驱链上,后继连上,去掉前驱后继之间的路径
就避免了O(n)的维护复杂度
#include<cstdio> #include<iostream> #include<cstring> #include<algorithm> #include<cstdlib> #include<set> #include<iterator> #define LL long long using namespace std; const int maxn=1e5+5; inline int read(){ int a=0;bool b=1;char x=getchar(); while(x<'0'||'9'<x){ if(x=='-')b=0; x=getchar(); } while('0'<=x&&x<='9'){ a=(a<<1)+(a<<3)+x-'0'; x=getchar(); } return b ? a : -a ; } int first[maxn],next[maxn*2],to[maxn*2],w[maxn*2]; int edge_count; inline void add(int x,int y,int z){ edge_count++; to[edge_count]=y; w[edge_count]=z; next[edge_count]=first[x]; first[x]=edge_count; } int n,m,size,dfn[maxn],T,num[maxn]; LL ans; void dfs(int u,int fa){ // if(u==3||u==2)printf("%d",T); num[++T]=u;dfn[u]=T; for(int i=first[u];i;i=next[i]){ int v=to[i]; if(v==fa)continue; dfs(v,u); } } int log[maxn],f[maxn][50],deep[maxn]; LL dis_root[maxn]; void build(int u,int fa){ deep[u]=deep[fa]+1; f[u][0]=fa; for(int i=1;i<=log[deep[u]];i++)f[u][i]=f[ f[u][i-1] ][i-1]; for(int i=first[u];i;i=next[i]){ int v=to[i]; if(v==fa)continue; dis_root[v]=dis_root[u]+w[i]; build(v,u); } } inline int LCA(int x,int y){ if(deep[x]<deep[y])swap(x,y); for(int i=log[deep[x]];i>=0;i--){ if(deep[y]<=deep[f[x][i]])x=f[x][i]; if(x==y)return x; } for(int i=log[deep[x]];i>=0;i--){ if(f[y][i]!=f[x][i]){ x=f[x][i];y=f[y][i]; } } return f[x][0]; } inline void LCA_init(){ deep[1]=1; for(int i=2;i<=n;i++)log[i]=log[i>>1]+1; build(1,0); } inline LL dis(int u,int v){ return dis_root[u]+dis_root[v]-2ll*dis_root[LCA(u,v)]; } //dfs序找距离最近的点 set<int >st; char ch[5]; int main(){ n=read(); for(int i=1;i<n;i++){ int x=read(),y=read(),z=read(); add(x,y,z);add(y,x,z); } LCA_init(); // printf("%d\n",LCA(6,5));//LCA ok dfs(1,0); // for(int i=1;i<=n;i++)printf("%d %d\n",dfn[i],i); m=read(); // printf("%d",m); for(int i=1;i<=m;i++){ scanf("%s",ch); // puts(ch); switch(ch[0]){ case'+':{ int t=read(); if(st.size()){ set<int >::iterator pos=st.lower_bound(dfn[t]); // printf("%d ",dfn[5]); // int nxt=(r==st.end()) ? 0 : (*r),pre=(l==--st.begin()) ? 0 : (*l) ; int nxt,pre; if(dfn[t]<*st.begin()){ nxt=*st.begin();pre=*(--st.end()); } else if(dfn[t]>*(--st.end())){ nxt=*(--st.end());pre=*st.begin(); } else nxt=*st.lower_bound(dfn[t]),pre=*(--st.lower_bound(dfn[t])); ans+=(dis(t,num[nxt])+dis(t,num[pre])-dis(num[pre],num[nxt])); } st.insert(dfn[t]); break; } case'-':{ int t=read(); // set<int >::iterator l=--st.lower_bound(dfn[t]),r=++st.lower_bound(dfn[t]); set<int >::iterator l,r=st.lower_bound(dfn[t]); l=r; int nxt=(r==(--st.end())) ? *st.begin() : *(++r),pre=(l==st.begin()) ? *(--st.end()) : *(--l); ans-=(dis(t,num[pre])+dis(t,num[nxt])-dis(num[nxt],num[pre])); // printf("%d %d\n",pre,nxt); st.erase(dfn[t]); break; } case'?':{ printf("%lld\n",ans/2ll); break; } } } return 0; }