驯龙高手 题解

发布时间 2023-09-06 16:55:06作者: 霜木_Atomic

驯龙高手 题解

原题题目不是这个但是我实在看不懂他起的这个题目是什么含义所以就重新起了。题目里的主角的名字我也换成了一个朋友的设定名字。

题目描述:

咪唑有 \(n\) 条龙 ,第 \(i\) 条龙的力量值为 \(x_i\)。咪唑想与这些龙交朋友。

但是龙很好斗,如果咪唑交上的朋友中,有两条龙力量值不相等,那么他们会打架。咪唑不想他的朋友打架。咪唑化学灰常厉害,所以他可以配药剂来改变龙龙们的力量值。咪唑有两种用同一原料配成的药剂,分别是:

  • 强化药剂:使某条龙的力量值增加 \(1\) 点,耗费 \(a\) 份原料;
  • 弱化药剂:使某条龙的力量值减少 \(1\) 点,耗费 \(b\) 份原料。

在第 \(i\) 次,咪唑会与前 \(i\) 条龙交朋友 \((1 \le i \le n)\)。我们有很多方案令前 \(i\) 条龙力量值相等。请找到耗费原料最少的方案,并输出耗费的原料数。

输入格式

第一行三个数 \(n\) \(a\) \(b\),表示龙的条数,强化药剂耗费的原料数,弱化药剂耗费的原料数。

第二行 \(n\) 个数,第 \(i\) 个数 \(x_i\) 表示第 \(i\) 条龙的力量值。

样例输入

5 3 2
5 1 4 2 3

样例输出

0
8
11
13
15

思路:

首先这个题感觉就是另一道题的加强版。我之前也写过题解。建议先看那道题再回来看这篇题解。

那道题可以看作这道题 \(a = b\) 的特殊情况,这时候每次取中位数来计算即可。可以用面积变化来证明。

那么 \(a \not= b\) 呢?其实也可以用类似的想法。还是考虑面积变化,如果让零值线上下移动的时候,左右面积不变,则只需要令左侧的底边长与右侧的底边长之比为 \(b:a\) 即可,也就是说,\(0\) 值要取在$\frac{b}{a+b}i $ 处。当然,保险起见,可以把这个点两侧的两个点的值都尝试一下,取最小的答案即可。

维护一个有序的序列,同时还要支持按排名查询,维护区间和,平衡树!

FHQ 轻松水过。复杂度 \(O(n \log n)\),常数很大……

代码:

#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+10;
const long long INF = 0x3f3f3f3f3f3f3f3f;
inline int read(){
	int x = 0; char ch = getchar();
	while(ch<'0' || ch>'9') ch = getchar();
	while(ch>='0'&&ch<='9') x = x*10+ch-48, ch = getchar();
	return x;
}

int n, A, B;

#define ll long long
struct node{
	int val, rnd, siz;
	ll sum;
	int ls, rs;
};
int root;
struct FHQ_Treap{
	node tree[N];
	#define ls(x) tree[x].ls
	#define rs(x) tree[x].rs
	int idx;
	int New(int val){
		tree[++idx] = (node){val, rand(), 1, val,  0, 0};
		return idx;
	}
	void push_up(int x){
		tree[x].siz = tree[ls(x)].siz+tree[rs(x)].siz+1;
		tree[x].sum = tree[ls(x)].sum+tree[rs(x)].sum+tree[x].val;
	}
	void split(int pos, int &l, int &r, int K){
		if(!pos) return l = r = 0, void();
		int tmp = tree[ls(pos)].siz+1;
		if(K >= tmp){
			l = pos;
			split(tree[pos].rs, tree[pos].rs, r, K-tmp);
			push_up(l);
		} else{
			r = pos;
			split(tree[pos].ls, l, tree[pos].ls, K);
			push_up(r);
		}
	}
	void split_by_val(int pos, int &l, int &r, int K){
		if(!pos) return l = r = 0, void();
		if(K >= tree[pos].val){
			l = pos;
			split_by_val(tree[pos].rs, tree[pos].rs, r, K);
			push_up(l);
		} else{
			r = pos;
			split_by_val(tree[pos].ls, l, tree[pos].ls, K);
			push_up(r);
		}
	}
	int merge(int l, int r){
		if((!l) || (!r)) return l | r;
		if(tree[l].rnd < tree[r].rnd){
			tree[l].rs = merge(tree[l].rs, r);
			push_up(l);
			return l;
		} else{
			tree[r].ls = merge(l, tree[r].ls);
			push_up(r);
			return r;
		}
	}
	void insert(int val){
		int dl,  dr;
		split_by_val(root, dl, dr, val);
		root = merge(merge(dl, New(val)), dr);
	} 
	int findr(int pos){
		while(rs(pos)){
			pos = rs(pos);
		}
		return pos;
	}
	int findl(int pos){
		while(ls(pos)){
			pos = ls(pos);
		}
		return pos;
	}
}fhq; 

int geta(int len){
	int ret = (1ll*len*B/(A+B));
	return ret;
}
int getb(int len){
	int ret = (int)ceil(1.0*(double)len*B/(A+B));
	return ret;
}

ll calc(int pos, int len){
	int dl, dr;
	fhq.split(root, dl, dr, pos);
	ll vl = fhq.findr(dl);
	ll vr = fhq.findl(dr);
	
	ll reta = INF, retb = INF;
	if(vl){
		vl = fhq.tree[vl].val;
		reta = 0;
		if(dl) reta += (vl * pos - fhq.tree[dl].sum)*A;
		if(dr) reta += (fhq.tree[dr].sum - vl * (len - pos))*B;
	}
	if(vr){
		vr = fhq.tree[vr].val;
		retb = 0;
		if(dl) retb += (vr * pos - fhq.tree[dl].sum)*A;
		if(dr) retb += (fhq.tree[dr].sum - vr * (len - pos))*B;
	}
	root = fhq.merge(dl, dr); 
	return min(reta, retb);
}
int main(){
	srand(time(0));
	n = read(), A = read(), B = read();
	for(int i = 1, tmp; i<=n; ++i){
		tmp = read();
		fhq.insert(tmp);
		int mda = geta(i);
		int mdb = getb(i);
		ll ans = 0x3f3f3f3f3f3f3f3f;
		if(mda > 0 && mda <=i) {
			ans = min(ans, calc(mda, i));
		}
		if(mdb > 0 && mdb <=i){
			ans = min(ans, calc(mdb, i));
		}
		printf("%lld\n", ans);
	}//A = B, FHQ, 每次按排名查中位数。 
	// 将序列分为 a:b 的两段即可www(保险起见 
	return 0;
}

后记:

赛时 1.5h 搞完的(大概 20min 花在了造数据测试上)……还是太菜了。赛后有很多做法,比如 \(O(n \log ^2n)\) 的二分斜率,还有其他的一个 \(\log\) 的做法,比如离散化后上树状数组或权值线段树;发现每次位置最多变化 \(1\) 后拿 set 和 map 搞;甚至可以直接拿对顶堆写(我感觉这个写法非常好)……

反正只有我最菜就对了 xwx。