P4090 [USACO17DEC]Greedy Gift Takers

$ \color{#0066ff}{ 题目描述 }$

有 N(\(1≤N≤10^5\))头牛按顺序排成一列,编号从 1 到 N,1 号牛在队头,N 号牛在队尾。

每次位于队头的牛 i 拿到一个礼物,然后插入到从队尾数\(c_i\)头牛之前的位置。。举个栗子: 初始队列 1,2,3,4,5 \(c_1\) = 2,\(c_2\) = 3,则第一次操作后的序列为 2,3,1,4,5,第二次操作后的序列为 3,2,1,4,5。重复无限次操作,求最后有几头牛拿不到礼物。

\(\color{#0066ff}{输入格式}\)

第一行一个整数n,接下来一行为\(c_i\)

\(\color{#0066ff}{输出格式}\)

输出一行为ans

\(\color{#0066ff}{输入样例}\)

3
1 2 0

\(\color{#0066ff}{输出样例}\)

1

\(\color{#0066ff}{数据范围与提示}\)

\(1 \leq N \leq 10^5\),

\(\color{#0066ff}{题解}\)

可以发现,如果一头牛无法拿到礼物,那么它后面的牛一定无法拿到礼物,因为相对顺序不变

于是这就具有了可二分性,我们可以二分那个最靠前的不能拿到礼物的牛是谁,就能获知答案

那么现在要考虑的就是如何判断一头牛能否拿到礼物

一头牛能拿到礼物,当且仅当它能排到队首,那么如何让它往前移动?? 其实便是前面的牛拿完礼物排在了它的后面,这样它其实就是往前了

可以开一个桶统计一下他前面的人的到达位置的个数的后缀和(开个桶统计到达位置,求后缀和)

那么\(t[i]\)就代表当前它前面有多少牛拿完礼物后可以到达i以及i之后的位置

那么当前它能移动的距离其实就是自己位置的桶,最后只要判它能不能到1即可

#include <cstdio>
#include <iostream>
#define LL long long
LL in() {
	char ch; LL x = 0, f = 1;
	while(!isdigit(ch = getchar()))(ch == '-') && (f = -f);
	for(x = ch ^ 48; isdigit(ch = getchar()); x = (x << 1) + (x << 3) + (ch ^ 48));
	return x * f;
}
template<class T> bool chkmax(T &a, const T &b) { return a < b? a = b, 1 : 0; }
template<class T> bool chkmin(T &a, const T &b) { return b < a? a = b, 1 : 0; }
const int maxn = 2e5 + 100;
int n, c[maxn], t[maxn];
bool ok(int x) {
	for(int i = 1; i <= n; i++) t[i] = 0;
	for(int i = 1; i <= x - 1; i++) t[n - c[i]]++;
	for(int i = n; i >= 1; i--) t[i] += t[i + 1];
	int now = x, nowtot = 0;
	for(int i = x; i >= 1; i--) {
		if(i < now) break;
		now = now - (t[i] - nowtot);
		nowtot += t[i] - nowtot;
	}
	return now != 1;
}
int main() {
	n = in();
	for(int i = 1; i <= n; i++) c[i] = in();
	int l = 1, r = n, ans = n + 1;
	while(l <= r) {
		int mid = (l + r) >> 1;
		if(ok(mid)) r = mid - 1, ans = mid;
		else l = mid + 1;
	}
	printf("%d\n", n - ans + 1);
	return 0;
}
posted @ 2019-04-12 07:58  olinr  阅读(516)  评论(0编辑  收藏  举报