A 国有 n 座城市,编号从 1 到 n,城市之间有 m 条双向道路。每一条道路对车辆都有重量限制,简称限重。
现在有 q 辆货车在运输货物,司机们想知道每辆车在不超过车辆限重的情况下,最多能运多重的货物。
第一行有两个用一个空格隔开的整数 n,m,表示 A 国有n 座城市和 m 条道路。
接下来 m 行每行 3 个整数 x、y、z,每两个整数之间用一个空格隔开,表示从 x 号城市到 y 号城市有一条限
重为 z 的道路。注意:x 不等于 y,两座城市之间可能有多条道路。
接下来一行有一个整数 q,表示有q 辆货车需要运货。
接下来 q 行,每行两个整数 x、y,之间用一个空格隔开,表示一辆货车需要从 x 城市运输货物到 y 城市,
注意:x 不等于 y。
对于 30%的数据,0 < n < 1,000,0 < m < 10,000,0 < q < 1,000;
对于 60%的数据,0 < n < 1,000,0 < m < 50,000,0 < q < 1,000;
对于 100%的数据,0 < n <10,000,0 < m < 50,000,0< q < 30,000,0 ≤ z ≤ 100,000。
输出共有 q 行,每行一个整数,表示对于每一辆货车,它的最大载重是多少。
如果货车不能到达目的地,输出-1。
4 3
1 2 4
2 3 3
3 1 1
3
1 3
1 4
1 3
3
-1
3
http://z6363636363.blog.163.com/blog/static/246696032201571053027123/
上面是《用Pascal的OIER的C语言常识》,(顺便安利一下我的博客,不要吐槽名字<_<)
贪心的思考一下,任意两点之间最大的载重一定在每个子图的最大生成树T上,因为如果不在T上,则这条路线最大的载重一定比原来路线的权值大,这与T是最大生成树相驳。
所以可以知道,首先构造最大生成树,然后对于每一组询问,查找最近公共祖先,输出最小边的权值即可,构造最大生成树相信都会吧,所以重点就落到了查找最近公共祖先上了,查找最近公共祖先,有两个常用的算法,离线的Tarjan算法,在线的树上倍增算法,什么是离线和在线呢?所谓的在线算法就是实时性的,比方说,给你一个输入,算法就给出一个输出,就像是http请求,请求网页一样。给一个实时的请求,就返回给你一个请求的网页。而离线算法则是要求一次性读入所有的请求,然后在统一得处理。而在处理的过程中不一定是按照请求的输入顺序来处理的。说不定后输入的请求在算法的执行过程中是被先处理的。
Tarjan算法和树上倍增算法
(再一次安利我的博客)
http://z6363636363.blog.163.com/blog/static/246696032201571083457357/#
这样看来,问题似乎已经完美解决,使用好理解的Tarjan算法就可以得到答案了,但是作为一个离线算法,Tarjan算法有一个劣势,就是会打乱输入数据的顺序,所以不好输出,做标记也许可以,但加大了思维负担,所以这里使用树上倍增算法,而树上倍增用到了位运算作为工具,所以。。。。位运算(最后一次安利。。。重要的东西要说三次)
http://z6363636363.blog.163.com/blog/static/246696032201571095240117/#
下面是我的C++代码:
#include
#include
#include
#include
#define inf0x7fffffff//定义一个认为的无限大的值,这是一个16进制数
using namespacestd;//上面的头文件什么的,可以无视
intn,m,q,cnt,tot,deep[10001],head[10001],f[10001],fa[10001][17],d[10001][17];
bool vis[10001];
struct edge{intx,y,v;}a[50001];//定义边的结构
struct e{intnext,to,v;}e[20001];//定义邻接表,关于邻接表,可以看ahalei的51CTO博客http://developer.51cto.com/art/201404/435072.htm
void ins(intu,int v,int w)
{e[++cnt].to=v;e[cnt].next=head[u];head[u]=cnt;e[cnt].v=w;}//向邻接表中加入一条边
void insert(intu,int v,int w)
{ins(u,v,w);ins(v,u,w);}//为了方便双向加入边,我这里写了一个驱动函数
int find(int x)
{returnf[x]==x?x:f[x]=find(f[x]);}//并查集的查,使用了三元表达式,翻译过来就是
/*
if(f[x]==x)
return x;
else
f[x]=find(f[x]);
return f[x];
*/
bool cmp(edgea,edge b)
{returna.v>b.v;}//为了使用C++内置的一个排序函数,定义边的比较,
void dfs(intx)//dfs给树上的每一个节点做上深度的标记
{
vis[x]=1;//标记当前节点已经走过
for(int i=1;i<=16;i++)
{
if(deep[x]<(1<
fa[x][i]=fa[fa[x][i-1]][i-1];//因为2^(j-1)+2^(j-1)=2^j,所以节点的2^(j-1)祖先的2^(j-1)祖先就是节点的2^j祖先
d[x][i]=min(d[x][i-1],d[fa[x][i-1]][i-1]);//记录下到第i祖先的最小路径权值
}
for(int i=head[x];i;i=e[i].next)//遍历邻接表,这里使用了for循环的语法糖
{
if(vis[e[i].to])continue;//如果当前边的到达顶点已经走过,就继续下一个边
fa[e[i].to][0]=x;//2^0=1,所以fa[i][0]是i节点的父亲,这里按照遍历边的顺序,把树建立(当前边的到达顶点的父亲就是出发顶点)
d[e[i].to][0]=e[i].v;//到达顶点到父亲的权值就是边的权值
deep[e[i].to]=deep[x]+1;//到达顶点的深度比父亲大1
dfs(e[i].to);//递归遍历到达顶点
}
}
int lca(intx,int y)//重点来了,这里是求树上两节点的最近公共祖先
{
if(deep[x] int t=deep[x]-deep[y];//得到两节点深度的差值 for(int i=0;i<=16;i++) if((1<
for(int i=16;i>=0;i--)//从int位的最后一位开始倒推,找到深度最大的公共祖先即为最近公共祖先 { if(fa[x][i]!=fa[y][i]) {x=fa[x][i];y=fa[y][i];}//在x与y的祖先不同时,同步上推 } if(x==y)return x;//处理一个边界情况,即如果在x上推到与y同等深度时,x与y相同,则公共祖先就是上推后的x return fa[x][0];//当同步上推完成时,x的父亲节点即为最近公共祖先(当然写y也行,不过我看x顺眼。。。) } int ask(intx,int f)//这里是询问x节点,到它的祖先节点的最小路径 { int mn=inf; int t=deep[x]-deep[f];//找到深度之差,确定上推范围 for(int i=0;i<=16;i++) { if(t&(1<
{ mn=min(mn,d[x][i]);//在上推中确定这条路线的最小权值 x=fa[x][i];//上推 } } return mn;//返回最小权值 } int main(void)//这里是主程序的开始 { freopen("truck.in","r",stdin); freopen("truck.out,","w",stdout);//上面是文件 memset(d,127/3,sizeof(d));//向记录路径权值的数组中填充一个大值 scanf("%d%d",&n,&m);//读入,n,m for(int i=1;i<=n;i++)f[i]=i;//初始化并查集 for(int i=1;i<=m;i++) scanf("%d%d%d",&a[i].x,&a[i].y,&a[i].v);//读入每一条边 sort(a+1,a+1+m,cmp);//将每一条边按照规定好的比较方式排序,完成后将按照降序排序 for(int i=1;i<=m;i++) { int x=a[i].x,y=a[i].y; int p=find(x),q=find(y); if(p!=q) { f[p]=q; insert(x,y,a[i].v); tot++;//记录使用的边的数量 if(tot==n-1)break;//如果使用的边已经有n-1个,那么最大生成树已经构造成功,跳出循环 }//这里是构造最大生成树。。。 } for(int i=1;i<=n;i++)if(!vis[i]){dfs(i);}//以第一个节点为根节点,遍历并标记每个顶点的深度(树的根节点可以随意选择,并不影响) scanf("%d",&q);//读入询问次数 for(int i=1;i<=q;i++) { int x,y; scanf("%d%d",&x,&y);//读入每一次询问 if(find(x)!=find(y)){printf("-1\n");continue;}//如果询问的两个顶点不在同一个连通分量中,那么就无路连通,输出-1 else { int t=lca(x,y);//找到询问的顶点的最近公共祖先 printf("%d\n",min(ask(x,t),ask(y,t)));//从x节点到祖先节点,从y节点到祖先节点,找到最小的权值,即为答案 } } return 0;//程序正确返回的标志 }