• [luogu1081] 开车旅行


    题面

    ​ 这个题目还是值得思考的.

    ​ 看到这个题目, 大家应该都想到了这样一个思路, 就是把每个点能够达到的最近的和次近的点都预处理出来, 然后跑就可以了, 现在问题就是难在这个预处理上面, 我们应该如何做呢? 观察到, 近的概念是两点之间的海拔距离最小, 所以我们可以将海拔距离从后往前(毕竟你只能往你后面的地方走嘛...), 扔进一个可以维护大小关系的数据结构中, 不妨设当前点海拔进入这个数据结构后位置为(k), 那么我们只需要比较位置为(k - 1), (k - 2), (k + 1), (k + 2)的四个数就可以了, 大家可以自己在纸上分类讨论一下, 这里就不做过多的阐述了. 现在我们只需要知道这个数据结构就可以了, 大家想一想, 有什么数据结构可以快速的找到某个数排序后的位置呢? 平衡树, 查找和插入的复杂度都为(log_ {2}{n})了, 这样我们就可以快速寻找了, 当然, 因为有重复结构, 所以用STL中的(multiset)是比较好的一种办法(其实是我不会双向链表), 这样我们就将比较难想的预处理部分弄出来了.

    ​ 当然, 想完了预处理后我们就可以想接下来怎么走了. 比较快速的方法是倍增, (f[i][j][opt])表示当前位置为(i), 已经走了(2 ^j)天, (opt)为1时代表从(i)位置开始走, 第一个走的是B所走到的位置, 那么(opt)(0)时就是A了. (disa[i][j][opt])代表在第(i)位出发, 走了(2 ^ j)天, (opt)为1代表从(i)开始走, B先出发, 在这段时间中A走的距离, opt为(0)自己推一下吧, 实在是不想打了, (disb[i][j][opt])也自己照着看一下吧. 我们在预处理出每个点的最近点和次近点后, 就可以处理出来了.

    ​ 至此, 预处理完毕, 剩下的代码中会说到的.

    具体代码

    #include <iostream>
    #include <cstring>
    #include <cstdio>
    #include <set>
    #define N 100005
    using namespace std;
    
    int n, x0, m, f[N][22][2], disa[N][22][2], disb[N][22][2], bin;
    struct node
    {
    	int id, h;
    	bool operator < (const node &p) const { return h < p.h; }
    } ht[N]; 
    
    multiset<node> s;
    multiset<node> :: iterator it; 
    
    inline int read()
    {
    	int x = 0, w = 1;
    	char c = getchar();
    	while(c < '0' || c > '9') { if (c == '-') w = -1; c = getchar(); }
    	while(c >= '0' && c <= '9') { x = x * 10 + c - '0'; c = getchar(); }
    	return x * w;
    }
    
    inline void get_ans(int S, int &dist_a, int &dist_b, int limit)
    {
    	int now = S;
    	for(int i = 20; i >= 0; i--)
    		if(f[now][i][0] && disa[now][i][0] + disb[now][i][0] + dist_a + dist_b <= limit)//倍增跑, 注意当没有能够跑的地方或者超过了给定的值的话就需要continue
    		{
    			dist_a += disa[now][i][0];
    			dist_b += disb[now][i][0];
    			now = f[now][i][0]; 
    		}
    }
    
    inline long long abs(long long x) { return x < 0 ? -x : x; }
    
    inline void init()
    {
    	for(int i = n; i >= 1; i--)
    	{
    		int Ato, Bto; node nxt, pre;
    		s.insert(ht[i]);//先插入当前海拔, 方便找
    		it = s.lower_bound(ht[i]);//找到当前海拔在序列中的位置
    		it++; nxt = (*it); 
    		it--; it--; pre = (*it);
    		it++;
            if(abs(nxt.h - ht[i].h) < abs(pre.h - ht[i].h))
            {
                it++, it++;
                Bto = nxt.id;
                if(abs(pre.h - ht[i].h) > abs((*it).h - ht[i].h)) Ato = (*it).id; 
                else Ato = pre.id;
            }
            else
            {//同上
                it--, it--;
                Bto = pre.id;
                if(abs(nxt.h - ht[i].h) >= abs((*it).h - ht[i].h)) Ato = (*it).id;
                else Ato = nxt.id; 
            }
            //注意, 这里由于前后海拔差相同的话取海拔较低的, 所以有的地方有等于号, 有的地方没有, 希望大家能够自己好好去想一下为什么, 有问题可以私信我(反正我不会回答的(手动删除)).
    		f[i][0][0] = Ato; f[i][0][1] = Bto;
    		disa[i][0][0] = abs(ht[Ato].h - ht[i].h); disb[i][0][0] = 0;
    		disb[i][0][1] = abs(ht[Bto].h - ht[i].h); disa[i][0][1] = 0;
    	}
    	for(int i = 1; i <= n; i++)//由于上面并没有讨论到A, B都走过了的情况, 只讨论了A和B单独走的情况, 故在这里要处理一下.
    	{
    		f[i][1][0] = f[f[i][0][0]][0][1];
    		f[i][1][1] = f[f[i][0][1]][0][0];
    		disa[i][1][1] = abs(ht[f[i][1][1]].h - ht[f[i][0][1]].h); disb[i][1][0] = abs(ht[f[i][1][0]].h - ht[f[i][0][0]].h);
    		disa[i][1][0] = disa[i][0][0]; disb[i][1][1] = disb[i][0][1]; //类似于倍增LCA中的预处理
    	}
    	for(int j = 2; j <= 20; j++)
    		for(int i = 1; i <= n; i++)
    		{
    			f[i][j][0] = f[f[i][j - 1][0]][j - 1][0];
    			f[i][j][1] = f[f[i][j - 1][1]][j - 1][1];
    			disa[i][j][0] = disa[i][j - 1][0] + disa[f[i][j - 1][0]][j - 1][0];
    			disb[i][j][0] = disb[i][j - 1][0] + disb[f[i][j - 1][0]][j - 1][0];
    			disa[i][j][1] = disa[i][j - 1][1] + disa[f[i][j - 1][1]][j - 1][0];
    			disb[i][j][1] = disb[i][j - 1][1] + disb[f[i][j - 1][1]][j - 1][0]; 
                //仔细思考一下, 应该不难
    		}
    }
    
    int main()
    {
    	n = read();
    	for(int i = 1; i <= n; i++) { ht[i].h = read(); ht[i].id = i; }
    	ht[0].h = 2e9 + 7; ht[n + 1].h = -(2e9 + 7); ht[0].id = 0; ht[n + 1].id = n + 1;
        //我取2147483647爆掉了, 调了整整一上午, 所以取极值的时候需要注意一下
    	s.insert(ht[0]); s.insert(ht[0]); s.insert(ht[n + 1]); s.insert(ht[n + 1]);
        //插入两个极小值和极大值代表我们在找到位置k的时候, 他前后总会至少有两个点, 免去了分类讨论的冗杂
    	init(); //预处理
    	x0 = read(); m = read();
    	int id = 0;
    	double ans = 1e16;
    	for(int i = 1; i <= n; i++)//枚举每一个点为起点时的比例
    	{
    		int dist_a = 0, dist_b = 0;
    		get_ans(i, dist_a, dist_b, x0);
    		if(dist_b == 0)
    		{
    			if(ans > 1e14) { ans = 1e14; id = i; }
    			else if(ans == 1e14 && ht[i].h > ht[id].h) id = i; 
    		}
    		else
    		{
    			double res = (double) dist_a / dist_b;
    			if(res < ans) { ans = res; id = i; }
    			else if(res == ans && ht[i].h > ht[id].h) id = i; 
    		}
    	}
    	printf("%d
    ", id);
    	for(int i = 1; i <= m; i++)
    	{
    		bin = read(); x0 = read();
    		int dist_a = 0, dist_b = 0;
    		get_ans(bin, dist_a, dist_b, x0);
    		printf("%d %d
    ", dist_a, dist_b); 
    	}
    	return 0;
    }
    
    

  • 相关阅读:
    天轰穿C# vs2010 04面向对象的编程之隐藏基类方法【原创】
    学云网助阵软博会 云教育平台备受关注
    天轰穿C# vs2010 04面向对象的编程之多态【原创】
    编程可以如此简单 学云网校园技术之旅
    天轰穿C# vs2010 04面向对象的编程之重载运算符【原创】
    天轰穿C# vs2010 04面向对象的编程之虚成员和重写【原创】
    天轰穿C# vs2010 04面向对象的编程之抽象类和抽象方法【原创】
    直播:1996年—2012年,哥从农民到清华大学出书的奋斗史
    天轰穿C# vs2010 04面向对象的编程之运算符重载的示例【原创】
    .天轰穿C# vs2010 04面向对象的编程之接口 VS 抽象类 【原创】
  • 原文地址:https://www.cnblogs.com/ztlztl/p/10447356.html
Copyright © 2020-2023  润新知