NOIP2016 提高二试 蚯蚓 题解

根据题意,每次都选取长度最长的蚯蚓将其切开,这不由的让我们想到了大顶堆和优先队列(其实优先队列的实质也是堆),但这种做法的复杂度却达到了O[M*log(N+M)],将题目最大数据规模代入约为1.4亿,这显然无法在1s的时间内通过。

那还有什么更好的算法能让我们快速的在一组数据中找到一个最大值呢?

假设存在长度分别为a[ i ]和a[ j ]的蚯蚓,令其满足i<j且a[ i ]>=a[ j ](可以认为a[]数组是单调非递增的)。

因为a[ i ]>=a[ j ]

所以a[ i ]*p>=a[ j ]*p (p>=0, p∈R)

所以int(a[ i ]*p)>=int(a[ j ]*p) (int()相当于下取整)

所以{ a[ i ] - int(a[ i ]*p) } >= { a[ j ] - int(a[ j ]*p }

也就是说,如果存储待切蚯蚓长度的数组a[]满足单调非递增,则其按题意产生的左右两段蚯蚓的长度一定也能够按被切的顺序单调非递增。分析到这里,有没有看出什么猫腻呢?没错,我们可以用单调队列解决!

我们开三个队列(在这我就不用STL了,用数组模拟)a[], x[], y[],分别存储原始蚯蚓长度(即读入的)、被切为左段的长度、被切为右段的长度。当然a[]是预先进行非递减排序的。这样,每1秒从三个队列选取值最大的队头出队,将其“切”成两段后再分别从队尾存入x[]和y[]。

 

代码如下:(max函数返回的是三个队列中最大队头的值,同时对队头最大的对列进行出队操作。)

 

const int MaxN = 100000+2;
const int MaxM = 7000000+2;

int a[MaxN], front_a, back_a;
int x[MaxN+MaxM], front_x, back_x;
int y[MaxN+MaxM], front_y, back_y;

int max(){
	int *front = NULL;
	int Max = 1<<31;
	if(front_a!=back_a && a[front_a]>Max){
		front = &front_a;
		Max = a[front_a];
	}
	if(front_x!=back_x && x[front_x]>Max){
		front = &front_x;
		Max = x[front_x];
	}
	if(front_y!=back_y && y[front_y]>Max){
		front = &front_y;
		Max = y[front_y];
	}
	++(*front);
	return Max;
}

最核心的问题解决了。现在还面临一个问题:每1秒除刚刚产生的蚯蚓外,其余长度全部增加q。如果按照题意来模拟,将其余全部队列中的元素扫描一遍并加q,单次操作的复杂度便达到了O(N+M),这是显然吃不消的。

 

还有更优的办法吗?

反过来想:他让我们把其余元素加q,那我们不妨把新产生的两个元素减q,这样各个元素之间的相对值是保持不变的。但是最终所有元素的值与实际值也肯定存在一个相对值“Plus”,我们只要最后把他加回来就好了。

代码如下:

 

bool flag = false;
int Plus = 0;
int cnt = 0;
for(int m = 1; m<=M; ++m){
	now = max()+Plus;
		
	if(m%T==0 && cnt<M/T){
		flag ? printf(" ") : flag = true;
		printf("%d", now);
		++cnt;
	}
		
	_x = int(now*P);    //left
	_y = now-_x;        //right
		
	Plus += Q;
	
	x[back_x++] = _x-Plus;
	y[back_y++] = _y-Plus;
}
printf("\n");


最主要的两个问题解决了,其他的估计也就顺理成章了吧!下附总代码

 

 

#include <cstdio>
#include <algorithm>
using namespace std;

const int MaxN = 100000+2;
const int MaxM = 7000000+2;

int N, M, Q, U, V, T;
int _x, _y, Plus;
int cnt, now;
double P;
bool flag;

int a[MaxN], front_a, back_a;
int x[MaxN+MaxM], front_x, back_x;
int y[MaxN+MaxM], front_y, back_y;

inline bool cmp(int a, int b){
	return a>b;
}

int max(){
	int *front = NULL;
	int Max = 1<<31;
	if(front_a!=back_a && a[front_a]>Max){
		front = &front_a;
		Max = a[front_a];
	}
	if(front_x!=back_x && x[front_x]>Max){
		front = &front_x;
		Max = x[front_x];
	}
	if(front_y!=back_y && y[front_y]>Max){
		front = &front_y;
		Max = y[front_y];
	}
	++(*front);
	return Max;
}

int main() {
	freopen("earthworm.in", "r", stdin); //freopen("earthworm.out", "w", stdout);

	scanf("%d %d %d %d %d %d", &N, &M, &Q, &U, &V, &T);
	P = U/double(V);
	
	for(int i = 0; i<N; ++i) scanf("%d", &a[back_a++]);
	sort(a, a+N, cmp);
	
	for(int m = 1; m<=M; ++m){
		now = max()+Plus;
		
		if(m%T==0 && cnt<M/T){
			flag ? printf(" ") : flag = true;
			printf("%d", now);
			++cnt;
		}
		
		_x = int(now*P);
		_y = now-_x;
		
		Plus += Q;
		
		x[back_x++] = _x-Plus;
		y[back_y++] = _y-Plus;
	}
	printf("\n");
	
	flag = false;
	cnt = 0;
	
	for(int m = 1; cnt<(N+M)/T; ++m){
		now = max()+Plus;
		if(m%T==0){
			flag ? printf(" ") : flag = true;
			printf("%d", now);
			++cnt;
		}
	}
	printf("\n");
	fclose(stdin); fclose(stdout); return 0;
}

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值