zoukankan      html  css  js  c++  java
  • [bzoj4144]Petrol——最小生成树+最短路

    题目大意:

    给定一个n个点、m条边的带权无向图,其中有s个点是加油站。
    每辆车都有一个油量上限b,即每次行走距离不能超过b,但在加油站可以补满。
    q次询问,每次给出x,y,b,表示出发点是x,终点是y,油量上限为b,且保证x点和y点都是加油站,请回答能否从x走到y。

    思路:

    不难发现如果要顺利地完成旅程,一定是从一个加油站跑到另外一个加油站去,并且任意两个加油站之间地距离不可以超过b。
    于是便转化成了这样一个问题:加油站为关键点,求一条路径使得两两关键点之间地路径长度的最大值最小
    这个时候最小生成树的性质便起到了作用,将两两加油站之间的最短距离计算出来,并且只对关键点求解最小生成树,生成树上的路径一定是最优方案,具体地证明可以参照Kruskal算法的运行过程。
    显然关键点的个数太多了,如果我们两两关键点之间求解最短路会T。。
    此时就要考虑最小生成树的性质,最大化地减少连接无用的边。
    考虑关键点s到关键点t的路径,若它们中途经过了另外一个关键点u,那么(s->u,u->t)一定会更优。
    如果s到t的路径可以被(s->a1,a1->a2...an->t,)这样的路径所取代,并且其中任意一条路径的长度均小于(s->t),那么(s->t)还是不要选择的好。
    于是我们可以对于每一个点求出离它最近的关键点,记为f[u],如果一条边(u,v)的f[u],f[v]不同,那么则将(f[u],f[v])加入候选边。
    如果一条关键点之间的路径不可以被上述方法得到,那么它肯定不能加入最小生成树中,即不满足上面的几个条件。
    即这条不符合条件的路径,要么路径中有其它的关键点,要么可以被一条其它的路径取代。
    具体地实现可以将所有关键点加入堆中跑Djkstra,然后对于所有询问离线即可。

    #include<bits/stdc++.h>
    
    #define REP(i,a,b) for(int i=a,i##_end_=b;i<=i##_end_;++i)
    #define DREP(i,a,b) for(int i=a,i##_end_=b;i>=i##_end_;--i)
    #define debug(x) cout<<#x<<"="<<x<<endl
    #define pii pair<ll,int>
    #define fi first
    #define se second
    #define mk make_pair
    typedef long long ll;
    
    using namespace std;
    
    void File(){
    	freopen("bzoj4144.in","r",stdin);
    	freopen("bzoj4144.out","w",stdout);
    }
    
    template<typename T>void read(T &_){
    	T __=0,mul=1; char ch=getchar();
    	while(!isdigit(ch)){
    		if(ch=='-')mul=-1;
    		ch=getchar();
    	}
    	while(isdigit(ch))__=(__<<1)+(__<<3)+(ch^'0'),ch=getchar();
    	_=__*mul;
    }
    
    const int maxn=2e5+10;
    int n,s,m,tot;
    int beg[maxn],to[maxn<<1],las[maxn<<1],cnte=1,fr[maxn];
    ll dis[maxn],w[maxn<<1];
    bool ans[maxn];
    priority_queue< pii,vector<pii>,greater<pii> >qu;
    
    void add(int u,int v,ll val){
    	las[++cnte]=beg[u]; beg[u]=cnte; to[cnte]=v; w[cnte]=val;
    	las[++cnte]=beg[v]; beg[v]=cnte; to[cnte]=u; w[cnte]=val;
    }
    
    struct Edge{
    	int u,v;
    	ll len;
    	int id;
    	bool operator < (const Edge & tt) const {
    		return len<tt.len;
    	}
    }E[maxn],Q[maxn];
    
    void Dijkstra(){
    	while(!qu.empty()){
    		int u=qu.top().se; ll d=qu.top().fi;
    		qu.pop();
    		if(d!=dis[u])continue;
    		for(int i=beg[u];i;i=las[i]){
    			int v=to[i];
    			if(d+w[i]<dis[v]){
    				fr[v]=fr[u];
    				dis[v]=d+w[i];
    				qu.push(mk(dis[v],v));
    			}
    		}
    	}
    }
    
    int q,fa[maxn];
    int find(int x){return fa[x]==x ? x : fa[x]=find(fa[x]);}
    
    void Kruskal(){
    	for(int i=2;i<=cnte;i+=2)
    		if(fr[to[i]]!=fr[to[i^1]])
    			E[++tot]=(Edge){fr[to[i]],fr[to[i^1]],dis[to[i]]+dis[to[i^1]]+w[i],0};
    	sort(E+1,E+tot+1);
    	REP(i,1,n)fa[i]=i;
    	read(q);
    	REP(i,1,q)read(Q[i].u),read(Q[i].v),read(Q[i].len),Q[i].id=i;
    	sort(Q+1,Q+q+1);
    	int p=0;
    	REP(i,1,q){
    		while(p<tot && E[p+1].len<=Q[i].len){
    			++p;
    			fa[find(E[p].u)]=find(E[p].v);
    		}
    		ans[Q[i].id]=(find(Q[i].u)==find(Q[i].v));
    	}
    	REP(i,1,q)if(ans[i])puts("TAK");
    	else puts("NIE");
    }
    
    void init(){
    	read(n); read(s); read(m);
    	memset(dis,63,sizeof(dis));
    	int u,v; ll val;
    	REP(i,1,s)read(u),qu.push(mk(0,u)),dis[u]=0,fr[u]=u;
    	REP(i,1,m)read(u),read(v),read(val),add(u,v,val);
    	Dijkstra();
    }
    
    int main(){
    	File();
    	init();
    	Dijkstra();
    	Kruskal();
    	return 0;
    }
    
    
  • 相关阅读:
    在编码转错的情况下,如何恢复
    【娱乐】给你的电脑检查兼容性,并获取你的电脑上安装的软件
    发布一个纯PHP的中文关键字自动提取工具
    [转]程序员能力矩阵 Programmer Competency Matrix
    解决PHP数组内存耗用太多的问题
    哈希表之数学原理
    PHP高级编程之单线程实现并行抓取网页
    如何自动的检测字符串编码
    如何检测网络中断, 并自动重启网卡
    完全二叉树判断,简单而复杂
  • 原文地址:https://www.cnblogs.com/ylsoi/p/9861110.html
Copyright © 2011-2022 走看看