最小行走距离(dfs+虚树)
好吧,感觉标题的名字取得有一点奇葩呢!源于我的高度概括
所以再做一些具体放阐述,这个问题大概意思是:在树上指定了一些点,你可以从树上的任意点出发,求走完所有指定点的最小行走距离(可存在一条边重复走,走几次这条边的权值就加几次)。
这个问题呢,我是在训练考试中碰到的,但是题目和要求做过一些小修改,为了更好的理解这种思想,我搜索到了这样一道最原始的题。
如何保证距离最短?我们可以做出这样的分析:
对于一个点,我们可以向子树走,也可以向父亲走。向父亲走肯定没有向子树走更优,因为你迟早要走一遍子树,先走父亲不过是又多加了一段重复走的路。(因为还要下来)
所以对于每一个点,我们选择先走子树再往上走->dfs序
而为什么说是虚树呢?其实我觉得不说虚树这个概念也没什么,因为我们只需要走那些被选择的点,而没有走完整棵树。
因为是动态的,我们考虑在当前状态下再加入一个点和删去一个点要怎么处理:
对于一条链,每一次变化时找到这个点插入的前驱pre和后继nxt,加入的时候删除dis(pre,nxt),加上dis(pre,x) + dis(x,pre),删除同理。
然后这个dfs序就用set维护
#include<bits/stdc++.h> #define N 100003 #define LL long long using namespace std; int read() { int x=0,f=1;char s=getchar(); while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();} while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();} return x*f; } struct EDGE{ int to,nextt,val; }w[N*2]; int vis[N],tot=0,head[N],now[N],cnt=0; int f[N][23],dep[N],id[N],pos[N];LL valuee[N]; LL ans=0; set<int>s; void add(int a,int b,int c) { tot++; w[tot].to=b; w[tot].nextt=head[a]; w[tot].val=c; head[a]=tot; } void dfs(int x) { id[++cnt]=x; pos[x]=cnt; for(int i=head[x];i;i=w[i].nextt) { int v=w[i].to; if(v==f[x][0])continue; f[v][0]=x;valuee[v]=valuee[x]+w[i].val; for(int i=1;i<=20;++i) f[v][i]=f[f[v][i-1]][i-1]; dep[v]=dep[x]+1; dfs(v); } } int lca(int x,int y) { if(dep[x]<dep[y])swap(x,y); for(int i=20;i>=0;--i)if(dep[f[x][i]]>=dep[y])x=f[x][i]; if(x==y)return x; for(int i=20;i>=0;--i)if(f[x][i]!=f[y][i])x=f[x][i],y=f[y][i]; return f[x][0]; } LL dis(int x,int y) { LL res=0; int LCA=lca(x,y); res+=valuee[x]+valuee[y]-2*valuee[LCA]; return res; } void modify(int x) { now[x]^=1; if(now[x]) s.insert(pos[x]); else s.erase(pos[x]); if(s.size()<=1){ans=0;return;}//存在只有一个和没有的情况,都要直接返回,这个时候就已经不存在前驱后继了 set<int>::iterator pre,nxt; pre=s.lower_bound(pos[x]);//大于等于 nxt=pre; if(now[x])nxt++; if(pre==s.begin())pre=--s.end(); else pre--; if(nxt==s.end()) nxt=s.begin(); LL dis1=dis(id[*pre],id[*nxt]); LL dis2=dis(id[*pre],x); LL dis3=dis(x,id[*nxt]); if(now[x])ans+=dis2+dis3-dis1; else ans-=dis2+dis3-dis1; } int main() { int n=read(),m=read(); for(int i=1;i<n;++i) { int a=read(),b=read(),c=read(); add(a,b,c);add(b,a,c); } dep[1]=1,dfs(1); for(int i=1;i<=m;++i) { int x=read(); modify(x); printf("%lld\n",ans); } }
然后看一下考试时遇到的有一点小变化的题
题面
初音未来的巡游
128MB / 1s ; cruise.cpp / c / pas / in / out
【题目描述】
Miku决定在n个地点中选择一些地点进行巡游。这n个地点由n-1条道路连接,两两之间有且仅有一条路径可以互相到达。Miku希望在这些道路中选择一些放上葱,使得Miku可以选择一种方式只经过有葱的道路而巡游完所有她选择的地点(一条道路可以被多次经过,起点任选)。
Miku想知道她至少需要准备多少葱。由于她的巡游计划可能更改,她希望你能在更改后即时回答她的疑问。
【输入格式】
第一行两个整数n,m,表示地点数和事件数。
第2至n行,每行两个整数x,y,表示地点x和地点y之间有一条无向道路。
接下来一行n个0/1数,若第i个数为1则表示i号地点初始时被选,为0则表示不选。
接下来一行m个整数,依次表示修改事件。第i个数Ai表示Ai号地点的状态发生变化,即若当前被选则改为不选,当前不选则改为被选。
【输出格式】
输出m行,第i行一个整数表示第i次事件后初音最少需要准备多少葱。
【样例数据】
cruise.in |
cruise.out |
5 8 1 2 1 3 2 4 2 5 1 0 0 1 0 5 4 2 1 2 5 3 2 |
3 2 2 1 0 0 0 2 |
【数据范围】
对于30%的数据,n,m≤3000。
对于另30%的数据,开始时所有地点都不选,保证修改操作的地点当前是不选状态。
对于100%的数据,1≤n,m≤200000,1≤x,y,Ai≤n。
不同的只是边权值全部为1,且只需要统计走过的路径条数,而不是走过的距离,简单分析我们可以发现,其实就是上一道题的ans/2,因为原来是走过了还要走回去,相当于走两次。
#include<bits/stdc++.h> #define N 200003 #define LL long long using namespace std; int read() { int x=0,f=1;char s=getchar(); while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();} while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();} return x*f; } struct EDGE{ int to,nextt; }w[N*2]; int vis[N],tot=0,head[N],now[N],cnt=0; int f[N][23],dep[N],id[N],pos[N]; int ans=0; set<int>s; void add(int a,int b) { tot++; w[tot].to=b; w[tot].nextt=head[a]; head[a]=tot; } bool dfs(int x) { id[++cnt]=x; pos[x]=cnt; bool back=now[x]; for(int i=head[x];i;i=w[i].nextt) { int v=w[i].to; if(v==f[x][0])continue; f[v][0]=x; for(int i=1;i<=20;++i) f[v][i]=f[f[v][i-1]][i-1]; dep[v]=dep[x]+1; if(dfs(v))//说明fa为v这条路径是要放葱的,因为下面有要选择的点,而到那个点的路径唯一,必须经过fa为v这条路 { ans++;//放葱的路径数++ back=1; } } return back; } int lca(int x,int y) { if(dep[x]<dep[y])swap(x,y); for(int i=20;i>=0;--i)if(dep[f[x][i]]>=dep[y])x=f[x][i]; if(x==y)return x; for(int i=20;i>=0;--i)if(f[x][i]!=f[y][i])x=f[x][i],y=f[y][i]; return f[x][0]; } int dis(int x,int y) { int LCA=lca(x,y); return dep[x]+dep[y]-2*dep[LCA]; } void modify(int x) { now[x]^=1; if(now[x]) s.insert(pos[x]); else s.erase(pos[x]); if(s.size()<=1){ans=0;return;}//存在只有一个和没有的情况,都要直接返回 set<int>::iterator pre,nxt; pre=s.lower_bound(pos[x]);//大于等于 nxt=pre; if(now[x])nxt++; if(pre==s.begin())pre=--s.end(); else pre--; if(nxt==s.end()) nxt=s.begin(); int dis1=dis(id[*pre],id[*nxt]); int dis2=dis(id[*pre],x); int dis3=dis(x,id[*nxt]); if(now[x])ans+=dis2+dis3-dis1; else ans-=dis2+dis3-dis1; } int main() { freopen("cruise.in","r",stdin); freopen("cruise.out","w",stdout); int n=read(),m=read(); for(int i=1;i<n;++i) { int a=read(),b=read(); add(a,b);add(b,a); } int go=0; for(int i=1;i<=n;++i) { now[i]=read(); if(now[i])go=i; } if(!go)dep[1]=1,dfs(1); else dep[go]=1,dfs(go);//从一个被选择的点开始走一定更优 ans=ans*2; for(int i=1;i<=n;++i) if(now[i]) s.insert(pos[i]); for(int i=1;i<=m;++i) { int x=read(); modify(x); printf("%d\n",ans/2); } }