山隐归林
2018-11-16 08:19:30
由于我的语文太菜了阅读本文章之前请小心断句
学习kruskal重构树的前置知识:
kruskal算法
并查集
树上倍增/Tarjan求lca
kruskal重构树基于kruskal算法,能巧妙地求解询问从A点走到B点的所有路径中,最长的边最小值是多少,或者最短的边的最大值之类的问题。
这个东西的本质其实就是把最小生成树上的边权改成了点权,原图的节点会增加n-1个,也会多出一些神奇性质。
以下是性质:
1.(只考虑新节点)根据以下的构造过程,kruskal重构树是一颗二叉树,并符合二叉堆的性质。
2.原树两点间的的最大边权就是kruskal重构树上两点的lca的权值。
3.重构树中代表原树中的点的节点全是叶子节点,其余节点都代表了一条边的边权。
根据这些美妙性质,kruskal重构树可以解决一些从A点走到B点的所有路径中,最长的边最小值是多少之类的傻逼问题
具体的实现:kruskal重构树就是在原本的kruskal算法中(先假设是求最小生成树),每次查到两个不在同一集合的点,就新开一个节点,然后把两个节点的祖先节点分别向新节点连边,不计边权,但是要记录新点的点权,就是连接两个点的边的边权。
这样一来,新点的点权的含义就是左右子树中的点想要互通,必须要走至少一条边权等于这个点权的边。
也就是说,给定两个点,他们想要联通,所经过的所有路径中最短的边的最大值,就是两个点在kruskal重构树上的lca的点权。
虽说kruskal重构树是基于kruskal算法得到的最小生成树上使用,但也可推广到图中两点路径中最短边的最大值的求解。
不理解的话可以拿稿纸手推,肯定是构不出反例的
若一个点能通过一条路径到达,那么我们走最小生成树上的边也一定能到达该节点。
这句话应该是那类傻逼问题的核心了吧
A国有n座城市,编号从1到n,城市之间有m条双向道路。每一条道路对车辆都有重量限制,简称限重。现在有q辆货车在运输货物, 司机们想知道每辆车在不超过车辆限重的情况下,最多能运多重的货物。
对于100%的数据0<n<10,000,0<m<50,000,0<q<30,000,0≤z≤100,0000<n<10,000,0<m<50,000,0<q<30,000,0≤z≤100,000。
转化问题:求两点之间所有路径中,最短的边的最大值。
Kruskal重构树模板题。每个询问实际上就是询问在最大生成树中的u,v之间的路径的最小边的权值。
构造重构树,边权按照从大到小的顺序排序,第一次使两个集合联通的边,一定是使这两个集合联通的最长的边,把边权当成重构树上的点权,就能转化成lca求解答案。
#include<bits/stdc++.h>//kruskal重构树,求两点之间所有的路径中,最短的边的最大值
using namespace std;
int n,m,q;
int nxt[200010],to[200010],head[50010],w[50010],tot;//重构树的边,w是新树点权
int cnt;
int f[50010];//kruskal算法的并查集
int fa[50010][20],dep[50010];//用于求lca
int vis[50010];
struct orz//用于kruskal算法
{
int x,y,val;
}a[100010];
char buf[1<<15],*fs,*ft;
inline char getc(){return (ft==fs&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),ft==fs))?0:*fs++;}
inline int read()
{
int x=0,f=1; char ch=getc();
while(!isdigit(ch)) {if(ch=='-') f=-1; ch=getc();}
while(isdigit(ch)) {x=(x<<1)+(x<<3)+(ch^48); ch=getc();}
return x*f;
}
void put(int x)
{
if(x==0){putchar('0');putchar('\n');return;}
if(x<0){putchar('-');x=-x;}
int num=0;char ch[16];
while(x) ch[++num]=x%10+'0',x/=10;
while(num) putchar(ch[num--]);
putchar('\n');
}
inline int my(orz a,orz b)
{
return a.val>b.val;
}
inline int find(int x)
{
int r=x,mid,j=x;
while(r!=f[r]) r=f[r];
while(j!=f[j]) mid=f[j],f[j]=r,j=mid;
return r;
}
inline void add(int xx,int yy)
{
to[++tot]=yy;nxt[tot]=head[xx];head[xx]=tot;
}
void dfs(int x)//预处理lca
{
vis[x]=1;
for(int ct=head[x];ct;ct=nxt[ct])
{
int y=to[ct];
fa[y][0]=x;
for(int i=1;i<=15;++i)
fa[y][i]=fa[fa[y][i-1]][i-1];
dep[y]=dep[x]+1;
dfs(y);
}
}
inline int lca(int x,int y)//求解lca
{
if(dep[x]<dep[y]) swap(x,y);
for(int i=15;i>-1;--i) if(dep[fa[x][i]]>=dep[y]) x=fa[x][i];
if(x==y) return x;
for(int i=15;i>-1;--i) if(fa[x][i]!=fa[y][i]) x=fa[x][i],y=fa[y][i];
return fa[x][0];
}
int main()
{
n=read();m=read();
for(int i=1;i<=m;++i)
a[i].x=read(),a[i].y=read(),a[i].val=read();
for(int i=1;i<=n;++i)f[i]=i,f[n+i]=n+i;//并查集初始化
cnt=n;sort(a+1,a+m+1,my);//kruskal算法+重构
for(int i=1;i<=m;++i)
{
int fx=find(a[i].x),fy=find(a[i].y);
if(fx!=fy)
f[fx]=f[fy]=++cnt,w[cnt]=a[i].val,add(cnt,fx),add(cnt,fy);
}
for(int i=cnt;i>n;--i)//预处理lca,注意原图可能不联通,所以我们可能构出了一个森林
if(!vis[i])
dep[i]=1,fa[i][0]=i,dfs(i);
q=read();//处理询问
for(int i=1;i<=q;++i)
{
int xx=read(),yy=read();
if(find(xx)!=find(yy)) put(-1);//两点可能不联通
else put(w[lca(xx,yy)]);
}
return 0;
}
由于题面太长所以不复制了,请自行查找题面。
这道题其实挺好的,巧妙糅合了许多算法和技巧,但均不超过NOIP难度,是一道良好的NOI送分题。(但是spfa已经死了)
解这道题的前置知识:
掌握了这些算法知识,就可以着手解决这道题了。
首先,我们可以用dijkstra求出1到所有点的距离。
我们注意到有一个海拔的参数,这决定我们从出生点可以跑到哪些地方。
转化:求出从出生点能到哪些点以及这些点到1的最短距离。
这时候我们拿出kruskal重构树这个东西(实在不好想)
按照海拔从高到低重构,由kruskal重构树的二叉堆性质我们发现限制越低我们就能到达越远的地方。
所以我们考虑在新树的节点上存储两个值:海拔、距离。
海拔是指以这个点为根的子树能够互相到达,并且只经过大于等于这个海拔的路径。这时候每个新树节点表示以它为根的子树中的节点可以互相到达,且经过的最低海拔就是该点权值。
距离是指以这个点为子树的所有节点到1的距离的最小值。
这时候我们发现距离和海拔两个参数都符合二叉堆的性质,也就是有单调性,就可以用倍增的算法了。
我们对一个出生点v,求出深度最小的u,使得v在以u为根节点的子树中,并且u点的海拔>限制海拔。
这个点存储的距离就是答案。
因为既然倍增到了u点,就已经保证了u点的子树之间都可以无代价互相到达,这棵子树的叶子就是开车能到达的节点。这些节点到1的距离的最小值自然就是答案。(因为总没有其他方法,使得到1步行的距离比先开车再走最短路的距离短了吧,否则就是在扇dijsktra算法的脸啊)
其他的就是细节问题了,注意本题强制在线以及T组数据,顺便计算好要开多大的数组。(因为数组开小RE两次)
#include<bits/stdc++.h>
using namespace std;
int nxt[1200010],to[1200010],head[600010],val[1200010],tot;
int w[600010],f[600010],fa[600010][21],dis[1200010],vis[1200010],cnt;
struct orz1
{
int x,y,l,a;
}a[400010];
int t,n,m,q,k,s;
int v,p,lastans;
char buf[1<<15],*ft,*fs;
inline char getc(){return (ft==fs&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),ft==fs))?0:*fs++;}
inline int read()
{
int num=0;char ch=getc();
while(!isdigit(ch))ch=getc();
while(isdigit(ch))num=(num<<1)+(num<<3)+(ch^48),ch=getc();
return num;
}
inline void put(int x)
{
if(x==0) {putchar('0');putchar('\n');return ;}
if(x<0) {putchar('-');x=-x;}
int num=0;char ch[16];
while(x) ch[++num]=x%10+'0',x/=10;
while(num) putchar(ch[num--]);
putchar('\n');
return ;
}
priority_queue<pair<int,int> > qq;
inline int my(orz1 a,orz1 b){return a.a>b.a;}
inline void add(int xx,int yy,int vv)
{
to[++tot]=yy,val[tot]=vv,nxt[tot]=head[xx],head[xx]=tot;
}
inline int find(int x)
{
int r=x,j=x,mid;
while(r!=f[r]) r=f[r];
while(j!=f[j]) mid=f[j],f[j]=r,j=mid;
return r;
}
inline void dijkstra(int st)
{
memset(dis,127,sizeof(dis));
dis[st]=0;
qq.push(make_pair(0,st));
while(!qq.empty())
{
int x=qq.top().second;
qq.pop();vis[x]=1;
for(int ct=head[x];ct;ct=nxt[ct])
{
int y=to[ct];
if(dis[y]>dis[x]+val[ct])
{
dis[y]=dis[x]+val[ct];
qq.push(make_pair(-dis[y],y));
}
}
}
}
inline int minmy(int a,int b){return a>b?b:a;}
void dfs(int x)
{
for(int ct=head[x];ct;ct=nxt[ct])
{
int y=to[ct];
fa[y][0]=x;for(int i=1;i<=20;++i)fa[y][i]=fa[fa[y][i-1]][i-1];
if(y>n)dfs(y);
dis[x]=minmy(dis[x],dis[y]);
}
}
inline int bei(int x)
{
int u=x;
for(int i=20;i>-1;--i) if(fa[u][i]&&w[fa[u][i]]>p) u=fa[u][i];
return dis[u];
}
int main()
{
t=read();
while(t--)
{
memset(head,0,sizeof(head));
memset(w,0,sizeof(w));
memset(fa,0,sizeof(fa));
memset(vis,0,sizeof(vis));tot=0;lastans=0;
n=read(),m=read();
for(register int i=1;i<=m;++i)
{
int xx=read(),yy=read(),vv=read(),aa=read();
a[i].x=xx,a[i].y=yy,a[i].l=vv,a[i].a=aa;
add(xx,yy,vv),add(yy,xx,vv);
}
dijkstra(1);
sort(a+1,a+1+m,my);
for(register int i=1;i<=n;++i) f[i]=i,f[n+i]=n+i;
cnt=n;
for(register int i=1;i<=m;++i)
{
int fx=find(a[i].x),fy=find(a[i].y);
if(fx!=fy)
{
f[fx]=f[fy]=++cnt;
add(cnt,fx,0);add(cnt,fy,0);
w[cnt]=a[i].a;
}
}
dfs(cnt);
q=read(),k=read(),s=read();
for(register int i=1;i<=q;++i)
{
v=(read()+k*lastans-1)%n+1,p=(read()+k*lastans)%(s+1);
lastans=bei(v);
put(lastans);
}
}
return 0;
}