洛谷 P1081 开车旅行 双向链表+倍增

第一步预处理出来每个城市最近的城市和第二近的城市,这一步怎么做呢?先按高度排序 O ( n l o g n ) O(nlogn) O(nlogn),再以排序后的元素建一个双向链表。这样按照原来读入的顺序枚举每个元素,找枚举元素的左一位,左两位,右一位,右两位这四个元素,如果出现没有某个元素的情况特判一下即可。枚举完一个元素就删除一个元素,这样保证双向链表中的元素在读入顺序中都是在枚举的元素的右边(或者是它本身)。这样我们就用 O ( n l o g n − n ) O(nlogn-n) O(nlognn)找出了每个元素右边最大值和次大值以及它们的位置。
第二步我们预处理出来三个 S T ST ST表。我们把a走一步和b走一步的操作合并到一起,视为操作了一步。开三个数组 t [ i ] [ j ] , S T a [ i ] [ j ] , S T b [ i ] [ j ] t[i][j],STa[i][j],STb[i][j] t[i][j],STa[i][j],STb[i][j] t [ i ] [ j ] t[i][j] t[i][j]表示从i号城市走,a和b分别走了 2 j 2^j 2j步,会到的城市的编号。 S T a [ i ] [ j ] STa[i][j] STa[i][j]表示从i号城市走,a和b分别走了 2 j 2^j 2j步,a在路上开的路程总数。 S T b [ i ] [ j ] STb[i][j] STb[i][j]表示从i号城市走,a和b分别走了 2 j 2^j 2j步,b在路上开的路程总数。
第三步就是解决问题的过程。非常简单的跑一下倍增的就好了。
这样我们利用倍增思想,在 O ( n l o g n − n − m l o g n ) O(nlogn-n-mlogn) O(nlognnmlogn)就是 O ( ( n + m ) l o g n ) O((n+m)logn) O((n+m)logn)时间复杂度解决了本题。

#include<cstdio>
#include<cstring>
#include<vector>
#include<queue>
#include<stack>
#include<algorithm>
using namespace std;
#define IL inline
typedef long long LL;
const int N = 1e5 + 3;
const int INF = 1e9 + 3;
const int LOG = 20;

struct Node{
	int h,p,l,r;
	bool operator < (const Node& rhs) const {
		return h < rhs.h || (h==rhs.h && p < rhs.p);
	}
	Node(int h=0,int p=0,int l=0,int r=0):h(h),p(p),l(l),r(r){}
};

struct Node1{
	int p,v,h;
	bool operator < (const Node1& rhs) const {
		return v < rhs.v || (v == rhs.v && h < rhs.h);
	}
	Node1(int p=0,int v=0,int h=0):p(p),v(v),h(h) {}
};

int n,m,s,log;
LL x, suma = 0, sumb = 0;
double minans = INF<<1;
int h[N],pos[N],ta[N],tb[N],da[N],db[N];
Node aa[N];

int t[N][LOG];
LL STa[N][LOG],STb[N][LOG];

IL void init() {
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%d",&h[i]);
	for(int i=1;i<=n;i++) aa[i] = Node(h[i],i,0,0);
	sort(aa+1,aa+1+n);
	for(int i=1;i<=n;i++) aa[i].l = i-1, aa[i].r = i+1;
	aa[1].l = aa[n].r = aa[0].l = aa[0].r = 0;
	for(int i=1;i<=n;i++) pos[aa[i].p] = i;
	
	for(int i=1;i<n;i++) {
		//get ta[i],tb[i],da[i],db[i]
		Node1 a[5]; int cnt=0;
		int p[] = {aa[pos[i]].l,aa[pos[i]].r,aa[aa[pos[i]].l].l,aa[aa[pos[i]].r].r};
		for(int j=0;j<4;j++) if(p[j]) {
			a[cnt++] = Node1(aa[p[j]].p,abs(aa[p[j]].h-aa[pos[i]].h),aa[p[j]].h);
		}
		sort(a,a+cnt);
		tb[i] = a[0].p; db[i] = a[0].v;
		if(i == n-1) continue;
		ta[i] = a[1].p; da[i] = a[1].v;
		
		//delete i.
		aa[aa[pos[i]].l].r = aa[pos[i]].r;
		aa[aa[pos[i]].r].l = aa[pos[i]].l;
	}
	
	//get t[i][0] & STa[i][0] & STb[i][0].
	for(int i=1;i<=n;i++) {
		t[i][0] = tb[ta[i]];
		STa[i][0] = da[i];
		STb[i][0] = db[ta[i]];
	}
	
	// get all of the ST table.
	for(int j=1;(1<<j)<=n;j++) {
		for(int i=1;i+(1<<j)-1<=n;i++) {
			t[i][j] = t[t[i][j-1]][j-1];
			STa[i][j] = STa[i][j-1] + STa[t[i][j-1]][j-1];
			STb[i][j] = STb[i][j-1] + STb[t[i][j-1]][j-1];
		}
	}
	
	//debug
	/*printf("ta[i]=\n");
	for(int i=1;i<=n;i++) printf("%d ",ta[i]);
	printf("\n");
	printf("tb[i]=\n");
	for(int i=1;i<=n;i++) printf("%d ",tb[i]);
	printf("\n");
	
	printf("t[i][j]=\n");
	for(int i=1;i<=n;i++) {
		for(int j=0;i+(1<<j)-1<=n;j++) {
			printf("%d ",t[i][j]);
		}
		printf("\n");
	}
	printf("STa[i][j]=\n");
	for(int i=1;i<=n;i++) {
		for(int j=0;i+(1<<j)-1<=n;j++) {
			printf("%d ",STa[i][j]);
		}
		printf("\n");
	}
	printf("STb[i][j]=\n");
	for(int i=1;i<=n;i++) {
		for(int j=0;i+(1<<j)-1<=n;j++) {
			printf("%d ",STb[i][j]);
		}
		printf("\n");
	}
	printf("\n");*/
}

IL void get_suma_sumb(int p) {
	suma = sumb = 0;
	for(int j=log;j>=0;j--) {
		if(t[p][j] && STa[p][j]+STb[p][j]+suma+sumb <= x) {
			suma += STa[p][j];
			sumb += STb[p][j];
			p = t[p][j];
		}
	}
	if(ta[p] && STa[p][0]+suma+sumb <= x) suma += STa[p][0];
}

IL void solve() {
	//problem 1
	scanf("%lld%d",&x,&m);
	int ans = n; minans = INF<<1;
	for(log=0;(1<<log)<=n;log++); log--;
	for(int i=1;i<=n;i++) {
		get_suma_sumb(i);
		if(sumb && 1.0 * suma / sumb < minans) {
			minans = 1.0 * suma / sumb;
			ans = i;
		}
	}
	printf("%lld\n",ans);
	
	//problem 2
	while(m--) {
		scanf("%d%lld",&s,&x);
		get_suma_sumb(s);
		printf("%lld %lld\n",suma,sumb);
	}
}

int main() {
	init();
	solve();
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值