• Luogu P1462 通往奥格瑞玛的道路


    Description:

    在艾泽拉斯大陆上有一位名叫歪嘴哦的神奇术士,他是部落的中坚力量
    有一天他醒来后发现自己居然到了联盟的主城暴风城
    在被众多联盟的士兵攻击后,他决定逃回自己的家乡奥格瑞玛
    在艾泽拉斯,有n个城市。编号为1,2,3,...,n。
    城市之间有m条双向的公路,连接着两个城市,从某个城市到另一个城市,会遭到联盟的攻击,进而损失一定的血量。
    每次经过一个城市,都会被收取一定的过路费(包括起点和终点)。路上并没有收费站。
    假设1为暴风城,n为奥格瑞玛,而他的血量最多为b,出发时他的血量是满的。
    歪嘴哦不希望花很多钱,他想知道,在可以到达奥格瑞玛的情况下,他所经过的所有城市中最多的一次收取的费用的最小值是多少。

    Analysis:

    这是一道二分答案
    二分答案的本质就是枚举,在已知解范围的情况下用二分的手段从解的范围中寻找出解
    题目:"他所经过的所有城市中最多的一次收取的费用的最小值是多少?"
    这句话的意思实际上是指:
    对于一条路径 a , 定义函数f(a)。
    对于路径上的所有点权构成的集合s, 满足f(a)=max(s)
    而对于一张图,从起点到终点存在多条路径a1,a2,a3...
    对于所有可能的路径a1,a2,a3...,均存在对应的f(a1),f(a2),f(a3)...
    求f(a1),f(a2),f(a3)...中的最小值
    基本的思想就是二分,二分什么呢?
    被二分的一定是一个包含解的集合
    首先,你的f(a)是等于max(s)的,f(a)一定是一个点权,即我们所求的解为一个点权
    而你走过的每一个路径中的最小点权f(a)一定在一个区间内:即整张图的最低点权与最高点权之间
    通过对整张图的点权进行排序,然后做二分,在整张图的点权集合中找出一个点权,这个点权是min( f(a1) f(a2) f(a3) ... ) 就是本题答案了
    我们已经找到了解的集合:点权集合。
    二分点权集合,每一次都会得到一手点权,这个点权将是路径上所有点点权的天花板
    在整张图上寻找路径,但是因为我们得到了一个天花板,所以点权大于这个天花板的点就不能选
    寻找路径也应该是最短路,因为你会扣血,所以需要找到扣血最少的路径
    要是找到的这个路径上的扣血总和依然致死,那么我们找到的点权就是无效的,继续向上二分,扩大点权范围
    要是不致死,那么分出的点权就是有效的,继续向下二分,缩小点权范围
    来自https://www.luogu.org/blog/user37455/solution-p1462

    Code

    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    #include<queue>
    #define N 10010
    #define INF 0x3f3f3f3f
    using namespace std;
    struct edge{
    	int to,next,w;
    }e[100100];
    int head[N],dis[N],vis[N],f[N],C[N],num_edge,n,m,b;
    bool Check(int x)
    {
    	queue<int> Q;
    	memset(dis,0x3f,sizeof(dis));
    	memset(vis,0,sizeof(vis));
    	dis[1] = 0;
    	vis[1] = 1;
    	Q.push(1);
    	while(!Q.empty())
    	{
    		int u = Q.front();Q.pop();
    		vis[u] = 0;
    		for(int i = head[u];i;i = e[i].next)
    		{
    			int v = e[i].to;
    			if((dis[v] > dis[u] + e[i].w) && f[v] <= x)
    			{
    				dis[v] = dis[u] + e[i].w;
    				if(!vis[v])
    				{
    					Q.push(v);
    					vis[v] = 1;
    				}
    			}
    		}
    	}
    	if(dis[n] < b) return 1;
    	return 0;
    }
    inline void add(int u,int v,int w)
    {
    	e[++num_edge].next = head[u];
    	e[num_edge].to = v;
    	e[num_edge].w = w;
    	head[u] = num_edge;
    }
    int main()
    {
    	scanf("%d%d%d",&n,&m,&b);
    	for(int i = 1;i <= n;++i)
    	{
    		scanf("%d",&f[i]);
    		C[i] = f[i];
    	}
    	for(int i = 1,u,v,w;i <= m;++i)
    	{
    		scanf("%d%d%d",&u,&v,&w);
    		add(u,v,w);
    		add(v,u,w);
    	}
    	sort(C + 1,C + 1 + n);
    	if(!Check(INF))
    	{
    		printf("AFK
    ");
    		return 0;
    	}
    	int lb = 1,ub = n + 1,ans = 0;
    	while(lb + 1< ub)
    	{
    		int mid = (lb + ub) / 2;
    		if(Check(C[mid])){
    			ub = mid;
    			ans = C[mid];
    		}
    		else lb = mid;
    	}
    	printf("%d
    ",ans);
    	return 0;
    }
    

    关于二分

    解的范围是整数,有两种方式

    • 左闭右开形式:
                int lb = 1, ub = N+1;
                while(lb + 1< ub){
                    int mid = (ub + lb) / 2;
                    if(judge(mid)) lb = mid;
                    else ub = mid;
                }
    
    • 左开右闭形式 :
                int lb = 0, ub = N;
                while(lb + 1< ub){
                    int mid = (ub + lb) / 2;
                    if(judge(mid)) ub = mid;
                    else lb = mid;
                }
    

    但是在所有情况下,上面的几种形式都是等价的呢? 答案是不是的。
    结论:上述形式的选取和judge对应的函数的单调性有关。如果judge函数是单调递增的,应该选取左开右闭形式。反之,如果judge函数是单调递减的,应该选取左闭右开的形式。

    作者:leodestiny
    来源:CSDN
    原文:https://blog.csdn.net/u012139398/article/details/38637311
    版权声明:本文为博主原创文章,转载请附上博文链接!

    岂能尽如人意,但求无愧我心
  • 相关阅读:
    阿里中间件——消息中间件Notify和MetaQ
    大型网站架构系列:分布式消息队列
    Mycat
    spring集成mybatis实现mysql读写分离
    mysql5.7.18的安装与主从复制
    NuGet学习笔记(3) 搭建属于自己的NuGet服务器
    NuGet学习笔记(1)——初识NuGet及快速安装使用
    Android 中使用 html 作布局文件
    获取WebView加载HTML时网页中的内容
    android学习——popupWindow 在指定位置上的显示
  • 原文地址:https://www.cnblogs.com/Zforw/p/10806129.html
Copyright © 2020-2023  润新知