克鲁斯卡尔重构树+noip2013货车运输+NOI2018归程
大概就是一个基于克鲁斯卡尔算法的一个算法?
先考虑一个模板题:不带修改的离线.
给出一个图,离线求任意两点间最大边的最小值.之前写过题解的.
大概就是先做出来一个最小生成树(森林),然后对它进行lca.我只树上倍增lca所以恰好解决了不修改的离线.
但是只止步于此是不行的,最小生成树的lca不能维护很多东西.我们需要用它来了解重构树.
如何重建呢?以本题为例,我们是在跑最小生成树的时候如过合并了就新建一个节点,把它和两个端点的祖先连起来(一般一个是重构树的节点,另一个是端点本身),更新自己点权为v=e[i].v;
确实有点丑了.毕竟第一次写.
对于样例:原图:
最大生成树:
重构一下下:(.号后的表示点权,红线是刚加上去的边)
对于这个红线构成的树(森林),显然可以跑lca找到最近公共祖先后直接输出点权.
#include<iostream> #include<iomanip> #include<cstdio> #include<cstdlib> #include<cstring> #include<cmath> #include<ctime> #include<string> #include<algorithm> #include<vector> #include<map> #include<stack> #include<queue> #include<deque> #include<set> using namespace std; char buf[1<<15],*fs,*ft; inline char getc(){ return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:* fs++; } inline int read(){ int This=0,F=1; char ch=getc(); while(ch<'0'||ch>'9'){ if(ch=='-') F=-1; ch=getc(); } while(ch>='0'&&ch<='9'){ This=(This<<1)+(This<<3)+ch-'0'; ch=getc(); } return This*F; } struct edge{ int x,y,v; int next; }o[500010],e[2000010]; int tx,ty; int n,m,sum,t; int head[20010],tot,v[200010],f[200010][20],fa[200010],d[200010]; queue<int>q; bool Orz(edge a,edge b){ return a.v>b.v; } int get(int x){ return fa[x]==x?x:fa[x]=get(fa[x]); } void add(int x,int y){ tot++; e[tot].x=x; e[tot].y=y; e[tot].next=head[x]; head[x]=tot; return ; } void bfs(int now){ q.push(now);d[now]=1; while(q.size()){ int x=q.front();q.pop(); for(int j=head[x];j;j=e[j].next){ int y=e[j].y; if(d[y])continue; d[y]=d[x]+1; f[y][0]=x; for(int i=1;i<=t;i++) f[y][i]=f[f[y][i-1]][i-1]; q.push(y); } } return ; } int lca(int x,int y) { if(d[x]>d[y])swap(x,y); for(int i=t;i>=0;i--) if(d[f[y][i]]>=d[x])y=f[y][i]; if(x==y)return x; for(int i=t;i>=0;i--) if(f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i]; return f[x][0]; } int main(){ n=read();m=read(); t=(int)log2(n*1.0)+1;//深度 for(int i=1;i<=n*2;i++)//并查集预处理 fa[i]=i; for(int i=1;i<=m;i++)//读入边 o[i].x=read(),o[i].y=read(),o[i].v=read(); sort(o+1,o+1+m,Orz);//从大到小排序 for(int i=1;i<=m;i++){//合并的同时让两端点的祖先连在sum+n上 if(get(o[i].x)!=get(o[i].y)){ sum++; v[sum+n]=o[i].v; add(get(o[i].x),sum+n); add(sum+n,get(o[i].x)); add(get(o[i].y),sum+n); add(sum+n,get(o[i].y)); fa[get(o[i].x)]=fa[get(o[i].y)]=sum+n; if(sum==n-1) break; } } for(int i=n+1;i<=n+sum;i++)//lca预处理 { if(fa[i]==i) bfs(i); } for(m=read();m;m--)//对于每个询问输出lca的v { tx=read();ty=read(); if(get(tx)!=get(ty)) cout<<-1; else cout<<v[lca(tx,ty)]; cout<<endl; } return 0; }
总之先通过这个简单题介绍了一下这个算法,下面来看一道难一点的题.
还记得去年暑假的时候装模做样的写NOI网络赛,第一题看到了这个就放弃了,水了一个最短路就弃疗了...
现在来认真的分析一下吧.
给出n个点和m条边,每条边都有自己的长度和海拔.对于每次询问,给出"起点"和当天的"水位线",所有海拔在水位线一下的点都有积水,你可以开车经过没有积水边,一旦要走有积水的边就要下车趟水,并且以后的边都要趟水,求最短的步行的路径.
能开车很好啊,我们想到了一个贪心:开车跑到当前能开到的距离1节点最近的位置.首先,求开车能跑到的位置好像是一个最大生成树,但是如何维护这个海拔呢?可以弄一个重构树来维护.
就比如样例:(长度.海拔)
来按照海拔做一个克鲁斯卡尔重构树:(新建节点后为点权)
这个图什么意思呢?比如5.2下管着2和3表示只有水位线小于2时2节点才能到达3,3节点才能到达2.再比如7.1下管着所有几点,它表示当水位线为0时你就可以开车从任意一个节点到达1了.
也就是说,我们先跑一个单源(1)最短路,然后按照海拔建立最大生成树的过程中在子树的根节点上维护子树内的点到达1的最小路程,显然节点越靠近根节点这个最小路程就会越小,并且包含着1节点的子树路程一定是0.
对于询问,你要根据当前的水位线尽可能的向上跳,然后输出那个最小路程就行.
代码很长调试很麻烦,但是一点一点来就好.什么时候我能克鲁斯卡尔重构树随便写呢?
using namespace std; struct edge{ int x,y,h; int next; }o[400010];//只是存储一下,开m也可 struct edge2{ int x,y,v,h; int next; }e[1600010];//这个结构体维护原来的图和新建重构树的边,开了4m int i,t,tx,ty,tv,th,lastans; int tnow,tp; int T,n,m,sum; int Q,K,S; int tot,head[400010];//这些数组维护的是原来图里的点和重构树新建的点,所以开2n即可 int d[400010],dis[400010],f[400010][20],minn[400010][20],fa[400010],v[400010],ans[400010]; priority_queue<pair<int,int>>q; queue<int>p; inline void add(int x,int y,int v){ tot++; e[tot].x=x; e[tot].y=y; e[tot].v=v; e[tot].next=head[x]; head[x]=tot; return ; } inline void add0(int x,int y,int h){ tot++; e[tot].x=x; e[tot].y=y; e[tot].h=h; e[tot].next=head[x]; head[x]=tot; return ; } inline bool Orz(edge a,edge b){ return a.h>b.h; } inline int get(int x){ return fa[x]==x?x:fa[x]=get(fa[x]); } void dfs(int now) { v[now]=1; ans[now]=min(ans[now],dis[now]); for(int j=head[now];j;j=e[j].next) { if(!v[e[j].y]) dfs(e[j].y),ans[now]=min(ans[now],ans[e[j].y]); } return ; } int main(){ for(T=read();T;T--) { tot=0;lastans=0;sum=0; memset(head,0,sizeof(head)); memset(d,0,sizeof(d)); memset(f,0,sizeof(f)); memset(v,0,sizeof(v)); n=read();m=read(); t=log2(n*1.0)+1; for(i=1;i<=2*n;i++)//并查集预处理 fa[i]=i; for(i=1;i<=m;i++){//读入边,存到o里准备最大生成树,存到e里准备Dijkstra tx=read();ty=read();tv=read();th=read(); o[i].x=tx;o[i].y=ty;o[i].h=th; add(tx,ty,tv); add(ty,tx,tv); } memset(dis,0x3f,sizeof(dis)); dis[1]=0; q.push(make_pair(0,1)); while(q.size()){//优先队列优化的Dijkstra tx=q.top().second;q.pop(); if(v[tx])continue; v[tx]=1; for(int j=head[tx];j;j=e[j].next){ ty=e[j].y; if(dis[ty]>dis[tx]+e[j].v){ dis[ty]=dis[tx]+e[j].v; q.push(make_pair(-dis[ty],ty)); } } } memset(head,0,sizeof(head)); memset(ans,0x3f,sizeof(ans)); tot=0;//再次启用e sort(o+1,o+1+m,Orz); sum=n; for(i=1;i<=m;i++){//最大生成树的同时克鲁斯卡尔重构树 if(get(o[i].x)!=get(o[i].y)){ sum++; add0(sum,get(o[i].x),o[i].h); add0(get(o[i].x),sum,o[i].h); add0(sum,get(o[i].y),o[i].h); add0(get(o[i].y),sum,o[i].h); fa[get(o[i].y)]=fa[get(o[i].x)]=sum; if(sum==2*n-1) break; } } memset(minn,0x3f,sizeof(minn)); p.push(get(1));d[get(1)]=1; while(p.size()){//bfs预处理f[x][k]表示第2^k层的祖先和minn[x][k]到达第2^k个祖先路上最小的海拔 tx=p.front();p.pop(); for(i=head[tx];i;i=e[i].next){ ty=e[i].y; if(d[ty])continue; d[ty]=d[tx]+1; f[ty][0]=tx; minn[ty][0]=e[i].h; for(int j=1;j<=t;j++){ f[ty][j]=f[f[ty][j-1]][j-1]; minn[ty][j]=min(minn[ty][j],minn[f[ty][j-1]][j-1]); } p.push(ty); } } memset(v,0,sizeof(v)); dfs(get(1));//dfs处理ans[x]表示以x为根节点的子树中的节点到达1节点的最短路径长度 Q=read();K=read();S=read(); for(;Q;Q--){//处理询问 tv=(read()+K*lastans-1)%n+1; th=(read()+K*lastans)%(S+1); for(i=t;i>=0;i--){//倍增找到自己能跳的最远的点 if(f[tv][i]&&minn[tv][i]>th) tv=f[tv][i]; } lastans=ans[tv]; write(lastans); putchar(10); } } return 0; }
在洛谷上1A了,开心.