NOIP2013 D1 T3 货车运输
好吧,遇上这种题,作为蒟蒻的我第一个想到的就是怎么打暴力,然而暴力都打不好QAQ!!!于是只能等教练讲解以后,然后在大犇的指导下终于做出来了。
对了,,好像还,没上题。。。。:
题目描述
A 国有 n 座城市,编号从 1 到 n,城市之间有 m 条双向道路。每一条道路对车辆都有重量限制,简称限重。现在有 q 辆货车在运输货物, 司机们想知道每辆车在不超过车辆限重的情况下,最多能运多重的货物。
输入输出格式
输入格式:输入文件名为 truck.in。
输入文件第一行有两个用一个空格隔开的整数 n,m,表示 A 国有 n 座城市和 m 条道
路。 接下来 m 行每行 3 个整数 x、 y、 z,每两个整数之间用一个空格隔开,表示从 x 号城市到 y 号城市有一条限重为 z 的道路。注意: x 不等于 y,两座城市之间可能有多条道路 。
接下来一行有一个整数 q,表示有 q 辆货车需要运货。
接下来 q 行,每行两个整数 x、y,之间用一个空格隔开,表示一辆货车需要从 x 城市运输货物到 y 城市,注意: x 不等于 y 。
输出格式:输出文件名为 truck.out。
输出共有 q 行,每行一个整数,表示对于每一辆货车,它的最大载重是多少。如果货
车不能到达目的地,输出-1。
输入输出样例
4 3 1 2 4 2 3 3 3 1 1 3 1 3 1 4 1 3
3 -1 3
说明
对于 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。
--------------------------------------------------------分割线--------------------------------------------------------
仔细思考一下,其实求最大载重量就是求出货车从某点到另一点的所有路径中最小权值最大的一条,于是可以用kruskal算法先处理出最大生成树,然后在树上进行倍增求lca,同时求出lca过程中经过的最小权值就行了。问题就在于如何在lca过程中求最小权值;很显然,直接在倍增过程中暴力求是行不通的,所以要预处理,在进行倍增预处理的同时记录下跳跃的该段中的最小权值,然后就可以很轻(ma)松(fan)的过了。如果还是有点不懂,请参考下列代码,其中有解释:
#include<cstdio> #include<iostream> #include<algorithm> #include<cstdlib> #include<cstring> #include<cmath> #define ll long long using namespace std; const int inf=0x7f7f7f7f; const int maxn=(int)1e5+10; int n,m,tot,g[maxn],head[maxn],fa[maxn][20],mm[maxn][20];
//mm数组记录每次跳跃过程中跳跃段中的最小权值 int f[maxn],dep[maxn],minn=0x7fffffff; bool vis[maxn]; struct Tree{ int to,val,next; }road[maxn]; struct Edge{ int f,t,v; }e[maxn];//邻接链表,不解释 inline int read()//读优,这个应该打得比较清楚 { char ch=getchar();int num=0;bool flag=false; if(ch<'0'||ch>'9') if(ch=='-') flag=true,ch=getchar(); else ch=getchar(); while(ch>='0'&&ch<='9') {num=num*10+ch-'0'; ch=getchar();} return flag?-num:num; } void clear(int pd)//对数组进行预先处理 { tot=0; if(pd) { memset(e,0,sizeof(e)); memset(mm,0x7f,sizeof(mm)); } memset(head,-1,sizeof(head)); for(int i=1;i<=n;i++) f[i]=i; } bool cmp(Edge a,Edge b) {return a.v>b.v;} int find(int x) { if(f[x]!=x) f[x]=find(f[x]); return f[x]; } void add(int u,int v,int w) { road[++tot].to=v; road[tot].val=w; road[tot].next=head[u]; head[u]=tot; } void build()//kruskal算法,如果不会,出门左转自行百度 { int a,b; sort(e+1,e+m+1,cmp); for(int i=1;i<=m;i++) { a=find(e[i].f); b=find(e[i].t); if(a!=b) { add(e[i].f,e[i].t,e[i].v); add(e[i].t,e[i].f,e[i].v); f[a]=b; } if(tot==m*2-2)break; } } void dfs(int x)
//深搜进行预处理,当然,预处理还有另外一种用两个变量的写法,我就不写了,可以到网上去看看
//据说那种写法比这种要好一点, { for(int i=1;i<=18;i++) { if(mm[x][i-1]<inf) mm[x][i]=min(mm[x][i-1],mm[fa[x][i-1]][i-1]);
/*这个应该不难理解,每次跳的时候都要更新最小值,切记,要在向上跳之前记录*/ fa[x][i]=fa[fa[x][i-1]][i-1]; } vis[x]=1; for(int i=head[x];i!=-1;i=road[i].next) { int y=road[i].to; if(y!=fa[x][0]) { dep[y]=dep[x]+1; mm[y][0]=road[i].val;
/*这里解释下,某一点跳一步的最小权值就是这两点间的权值*/ fa[y][0]=x; dfs(y); } } } int lca(int u,int v)//倍增求lca,不解释 { if(dep[u]<dep[v]) swap(u,v); int d=dep[u]-dep[v]; for(int i=0;i<=18;i++) if(d&(1<<i)) { minn=min(minn,mm[u][i]); u=fa[u][i]; } if(u==v) return minn; for(int i=18;i>=0;i--) { if(fa[u][i]!=fa[v][i]) { minn=min(minn,min(mm[u][i],mm[v][i])); u=fa[u][i]; v=fa[v][i]; } } minn=min(minn,min(mm[u][0],mm[v][0])); return minn; } int main() { int x,y,q; scanf("%d%d",&n,&m); clear(1); for(int i=1;i<=m;i++) { e[i].f=read(); e[i].t=read(); e[i].v=read(); } scanf("%d",&q); build();//最大生成树 memset(vis,false,sizeof(vis)); for(int i=1;i<=n;i++) if(!vis[i]) dfs(i);//进行lca倍增预处理 for(int i=1;i<=q;i++) { scanf("%d%d",&x,&y); minn=inf; if(find(x)!=find(y)) {printf("-1\n");continue;} printf("%d\n",lca(x,y)); } return 0; }
-----------------------------------------------------------------分割线-----------------------------------------------------------------------
作为第三题,果然还是有点难度的,神犇的路还很漫长,加油吧!
如需转载,请署名作者并附上原文链接,蒟蒻非常感激
名称:HolseLee
博客地址:www.cnblogs.com/cytus
个人邮箱:1073133650@qq.com