HIT Winter Day1 Contest

HIT Winter 20190112 - 二分 三分 贪心

[比赛链接][https://vjudge.net/contest/278692]

A

Description

判断能否经过\(s\)步从\((0,0)\)走到\((a,b)\),每次只能走到上下左右相邻的格子。

Solution

注意\(a\)\(b\)可能是负数。判断\(s\)\(|a| + |b|\)之差的奇偶性即可。

不要用库文件里的abs函数,手写保险。

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

#define rep(i, a, b) for (int i = a; i <= b; i++)

int a, b, s;

int get_abs(int x) { return x >= 0 ? x : -x; }

int main()
{
	scanf("%d%d%d", &a, &b, &s);
	int step = get_abs(a) + get_abs(b);
	printf(((s - step) % 2 == 1 || s < step) ? "No" : "Yes");
	return 0;
}

B

Description

对于一个长度为\(n\)的序列有\(C(n, 2)\)\(|a_i - a_j|\)。将这些差值排序,问最中间的(第\(C(n, 2) / 2\)个)差值是多少。

Solution

\(a_i\)排序后二分答案\(x\),通过计算\(x\)在差值中的“排名”,移动二分的上下界。对于每个\(a_i\),用lower_bound找出有多少在\(a_i\)\(a_i+x\)之间的数,这个个数记为\(c_i\)。将所有\(c_i\)相加,得到序列中差值小于等于\(x\)的数对个数,也就是\(x\)这个差值的“排名”。

考场上一度这个问题转化成了第\(k\)大连续和问题,差点一个主席树就写上去了(菜狗.jpg

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

 #define rep(i, a, b) for (int i = a; i <= b; i++)

typedef long long LL;

const int N = 100000 + 5, maxX = 1000000000;

int n, k;
int a[N];

bool judge(int x) {
	int sum = 0;
	rep(i, 1, n) {
		int val = a[i] + x;
		int range = lower_bound(a + 1, a + n + 1, val) - (a + i + 1);
		sum += range;
	}
	return sum < k;
}

int main()
{
	while (scanf("%d", &n) == 1) {
		k = (LL)n * (n - 1) / 2;
		k = (k % 2 == 1) ? k / 2 + 1 : k / 2;
		rep(i, 1, n) scanf("%d", &a[i]);
		sort(a + 1, a + n + 1);
		int l = 0, r = maxX + 1;
		while (l + 1 < r) {
			int mid = l + (r - l) / 2;
			if (judge(mid)) l = mid; else r = mid;
		}
		printf("%d\n", l);
	}

	return 0;
}


C

Description

\(n\)场考试,给出每场答对的题数\(a\)和这场一共有几道题\(b\),求去掉\(k\)场考试后,求加权平局值的最大值。

Solution

(如果工大也来一次这个活动该多好(幻想

裸的01分数规划。

二分答案\(x\)。我们希望选出\(n-k\)门课(设选出的集合为\(S\)),使得\(\sum{a_j}/\sum{b_j} \ge x\),其中\(j \in S\)

移项,有\(\sum{a_j} - x\sum{b_j} \ge 0\)

\(f(j) = a_j - xb_j\),则\(\sum{f(j)} \ge 0\)

这样就可以贪心了,选出\(f(j)\)最大的\(n-k\)门课,计算\(f\)的和,判断其是否非负即可。

注意"%.0f"这种占位符输出的就是四舍五入的结果,不用+0.5。

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

#define rep(i, a, b) for (int i = a; i <= b; i++)
#define dep(i, a, b) for (int i = a; i >= b; i--)

const int N = 1000 + 5;
const double eps = 1e-5;

double sum1, sum2;
double f[N];
int n, k;
int a[N], b[N];

double dabs(double x) { return x > 0 ? x : -x; }

double calc_sum(double x) {
	rep(i, 1, n) f[i] = (double)a[i] - (double)b[i] * x;
	sort(f + 1, f + n + 1);
	double sum = 0;
	dep(i, n, k + 1) sum += f[i];
	return sum;
}

int main()
{
	while (scanf("%d%d", &n, &k) == 2) {
		if (n == 0 && k == 0) break;
		rep(i, 1, n) scanf("%d", &a[i]);
		rep(i, 1, n) scanf("%d", &b[i]);

		double l = 0, r = 1;
		while (r - l > eps) {
			double mid = l + (r - l) / 2;
			double sum = calc_sum(mid); 
			if (sum < 0) r = mid; else l = mid;
		} 

		printf("%.0f\n", l * 100);
	}
	return 0;
}


D

Description

\(n\)滩泥,需要用木板覆盖。给出每摊泥的起始位置和木板长度\(l\),求最少需要多少木板才能覆盖这些泥。

Solution

简单贪心:“尽量往右”思想。

代码中用一个变量beg表示当前这摊泥需要从哪里开始覆盖。

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

#define rep(i, a, b) for (int i = a; i <= b; i++)
#define mp make_pair

const int N = 10000 + 5;

int n, l, x, y;
pair<int, int> mud[N];

int main()
{
	scanf("%d%d", &n, &l);
	rep(i, 1, n) {
		scanf("%d%d", &x, &y);
		mud[i] = mp(x, y);
	}
	sort(mud + 1, mud + n + 1);

	int beg = mud[1].first;
	int ans = 0;
	rep(i, 1, n) {
		int cur = (mud[i].second - beg) / l;
		if (cur * l < mud[i].second - beg) cur++;
		ans += cur;
		if (i < n)
			beg = max(mud[i + 1].first, beg + l * cur);
	}
	printf("%d\n", ans);
	
	return 0;
}


E

Description

\(n\)个人要渡河,但是只有一艘船,船上每次最多只能载两个人,渡河的速度由两个人中较慢的那个决定,小船来回载人直到所有人都渡河,求最短的渡河时间。

Solution

\(n\)个人按渡河时间从小到大排序。设\(f(i)\)表示前\(i\)个人完成渡河(全部到达右岸)所需的最少时间。

显然,最快的人和次快的人应该先划到右岸。考虑第\(i\ (i>=3)\)个人渡河(从左岸到右岸),有两种策略:

  • 最快的人把船从右岸划回左岸,带着\(i\)划回右岸。
  • 后退一步,假设\(i-1\)还在左岸。最快的人留在右岸,次快的人把船从右岸划回左岸,\(i\)\(i-1\)一起划船到右岸。然后最快的人划回左岸,带着次快的人划到右岸。

取较优者即可。

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

#define rep(i, a, b) for (int i = a; i <= b; i++)

const int N = 1000 + 5;

int T, n;
int a[N], f[N];

int main()
{
	scanf("%d", &T);
	while (T--) {
		scanf("%d", &n);
		rep(i, 1, n) scanf("%d", &a[i]);
		sort(a + 1, a + n + 1);
		f[1] = a[1];
		f[2] = a[2];
		rep(i, 3, n)
			f[i] = min(f[i - 1] + a[1] + a[i], f[i - 2] + a[2] + a[i] + a[1] + a[2]);
		printf("%d\n", f[n]);
	}
	return 0;
}


F

Description

给定一个序列A。一个区间的poorness定义为该区间元素和的绝对值。序列A的weakness等于所有区间最大的poorness。求一个\(x\)使得,序列A全部减\(x\)后weakness最小。\(1 \le n \le 200000\)

Solution

直觉上weekness是关于\(x\)的凹函数。直觉是对的。三分答案即可。

注意这里二分的结束条件没有用\(r-l<{\rm eps}\),而是是相邻两次计算得到的weekness小于eps。

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

#define rep(i, a, b) for (int i = a; i <= b; i++)

const int maxA = 10000, N = 200000 + 5;
const double eps = 1e-8;

int n;
int a[N];
double d[N];
double w1, w2;

double dabs(double x) { return x > 0 ? x : -x; }

double get_weekness(double x, double sgn) {
	double res = 0;
	d[0] = 0;
	rep(i, 1, n) {
		d[i] = max(d[i - 1], 0.0) + (a[i] - x) * sgn;
		res = max(res, d[i]);
	}
	return res;
}

int main()
{
	scanf("%d", &n);
	rep(i, 1, n) scanf("%d", &a[i]);

	double l = -(maxA + 1), r = maxA + 1;
	do {
		double mid1 = l + (r - l) / 2;
		double mid2 = mid1 + (r - mid1) / 2;
		w1 = max(get_weekness(mid1, 1), get_weekness(mid1, -1));
		w2 = max(get_weekness(mid2, 1), get_weekness(mid2, -1));
		if (w1 < w2) r = mid2; else l = mid1;
	} while (dabs(w1 - w2) > eps);
    
	printf("%.9f\n", max(get_weekness(l, 1), get_weekness(l, -1)));
	return 0;
}


G

Description

一条长\(L\)的河上, 除了\({\rm START}\)\({\rm END}\) 还有\(N\)个石子,分别距离起点距离\(d_i\),求去掉\(M\)个石子后相邻的最小距离的最大值。

Solution

NOIP原题。

二分答案\(x\)。问题转化成,判断:能否去掉\(M\)个石子,使得任意两个相邻石子间距离都不小于\(x\)。这个简单循环判断即可。注意可能连续去掉多个石子,也别忘判断起点到第\(1\)个石子、第\(n\)个石子到终点是否满足条件。

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

#define rep(i, a, b) for (int i = a; i <= b; i++)

const int N = 500000 + 5;

int L, n, m;
int a[N], t[N];

int main()
{
	scanf("%d%d%d", &L, &n, &m);
	rep(i, 1, n) scanf("%d", &a[i]);
	a[0] = 0; a[n + 1] = L;
	sort(a + 1, a + n + 1);
	
	int l = 0, r = L + 1;
	while (l + 1 < r) {
		int mid = l + (r - l) / 2;
		
		bool flag = true;
		int rest = m;
		rep(i, 0, n + 1) t[i] = a[i];
		rep(i, 1, n + 1) 
			if (t[i] - t[i - 1] < mid) {
				rest--;
				if (rest < 0) {
					flag = false;
					break;
				}
				t[i] = t[i - 1];
			}
		if (flag) l = mid; else r = mid;
	}
	printf("%d\n", l);
	return 0;
}


H

Description

\(n\)朵花,每朵花都有一定的高度\(a_i\)\(m\)天之后要把这些花送给别人。这\(m\)天里可以通过淋花来让花长得尽可能高。每天只能淋一次,一次覆盖的范围是连续的\(w\)朵,淋完水后被淋的花会在当天长高\(1\)个单位。要求经过\(m\)天后,最大化最矮的花的高度。

Solution

此题令我身败名裂。二分之后的判断一直思路混乱。

二分答案\(x\),设\(len_i = x - a_i\)。问题变成,判断:能否将一个长度为\(n\)的全0序列,经过\(m\)\(w\)区间加1操作后,第\(i\)个位置上的元素不小于\(len_i\)

是不是很像某年NOIP的堆积木?这里只多了一个限制条件,即操作区间的长度\(w\)。最简洁的判断方法是,用类似借教室的前缀和技巧。

代码中用\({\rm add}\)表示增减标记,\({\rm val}\)表示前缀和,即当前元素被覆盖的次数。

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

#define rep(i, a, b) for (int i = a; i <= b; i++)
#define mp make_pair ;

typedef long long LL;

const int N = 100000 + 5;
const LL maxA = 1000000000;

typedef pair<int, int> Pii;
int n, m, w, head, tail;
int a[N], len[N], add[N], val[N];
Pii q[N];

bool judge(int x) {
	rep(i, 1, n) len[i] = max(0, x - a[i]);
	
	LL sum = 0;
	memset(add, 0, sizeof(add));
	memset(val, 0, sizeof(val));

	rep(i, 1, n) {
		val[i] = val[i - 1] + add[i];
		if (val[i] < len[i]) {
			add[i] = (len[i] - val[i]);
			if (i + w <= n) add[i + w] = -(len[i] - val[i]);
			val[i] += add[i];
		}
		if (add[i] > 0) sum += add[i];
	}
	if (sum > m) return false;
	return true;
}

int main()
{
	scanf("%d%d%d", &n, &m, &w);
	rep(i, 1, n) scanf("%d", &a[i]);

	int l = 0, r = maxA + m + 1;
	while (l + 1 < r) {
		int mid = l + (r - l) / 2;
		if (judge(mid)) l = mid; else r = mid;
	}
	printf("%d\n", l);
	return 0;
}


I

Description

给你\(N\)个机器和\(M\)个任务, 每个任务有两个值花费时间\(x\)和难度\(y\), 每个机器也有两个值最大工作时间\(x'\)和最大工作难度\(y'\), 机器可以胜任某个工作的条件是\(x' \ge x\)\(y' \ge y\),机器胜任一个工作可以拿到\(500x+2y\)的钱,现在问你怎么匹配才能使匹配数最大,匹配数相同时要求钱数最多。

Solution

典型的双属性贪心题。

我们发现,对于同样的机器资源,完成工作时间更多的任务总是划算的。假设有任务A\((x_a, y_a)\),任务B\((x_b, y_b)\),且\(x_a > x_b, y_a < y_b\)。由于\(x\)的权重为500而\(y\)的权重为2(且\(y\)最大值仅有100),所以即便\(x_b\)只小1,\(y_b\)大100,做任务B也没有做任务A划算。

下面考虑如何匹配。对任务和机器都以工作时间为第一关键字、难度为第二关键字排序。依次考虑排序后的每个任务,我们维护一个set,set中存放工作时间满足当前任务的机器,set内部按难度排序。对于当前任务,我们贪心地匹配\(y'\)大于等于\(y\)且最小的机器即可。

要注意set.end()不是尾元素,end()的前驱才是

再注意set中只剩一个元素时erase()它会RE,要用clear()。

还要注意此题钱数会爆int。

Code
#include <cstdio>
#include <cstring>
#include <cmath>
#include <cstdlib>
#include <algorithm>
#include <vector>
#include <set>
using namespace std;

#define rep(i, a, b) for (int i = a; i <= b; i++)
#define dep(i, a, b) for (int i = a; i >= b; i--)
#define fill(a, x) memset(a, x, sizeof(x))
#define mp make_pair
#define pb push_back

typedef long long LL;
typedef unsigned long long uLL;
typedef pair<int, int> Pii;

const int N = 100000 + 5, M = 100000 + 5;

Pii mach[N], task[M];

bool cmp1(Pii x, Pii y) { return x > y; }

bool cmp2(int x, int y) { return mach[x].second < mach[y].second; }

multiset<Pii> ready;
multiset<Pii>::iterator iter;

int n, m;

int get_max_level() {
	multiset<Pii>::iterator iter = ready.end();
	iter--;
	return iter->first;
}

int main()
{
	while (scanf("%d%d", &n, &m) == 2) {

		rep(i, 1, n) scanf("%d%d", &mach[i].first, &mach[i].second);
		rep(i, 1, m) scanf("%d%d", &task[i].first, &task[i].second);
		sort(mach + 1, mach + n + 1, cmp1);
		sort(task + 1, task + m + 1, cmp1);

		int j = 1, ans1 = 0;
		LL ans2 = 0;
		ready.clear();
		rep(i, 1, m) {

			while (mach[j].first >= task[i].first && j <= n) {
				ready.insert(mp(mach[j].second, mach[j].first));
				j++;
			}

			if (ready.size() == 0 || get_max_level() < task[i].second) continue;
			iter = ready.lower_bound(Pii(task[i].second, 0));

			ans1++;
			ans2 += task[i].first * 500LL + task[i].second * 2LL;

			if (ready.size() == 1) ready.clear(); else ready.erase(iter);
		}

		printf("%d %lld\n", ans1, ans2);
	}
	
	return 0;
}


说点什么

最后还是脱不了俗套,把算法竞赛拾起来了……

Day1手还是比较生,写什么错什么,也有一些东西还没想清楚就急急忙忙写,H题没肝出来整个比赛节奏就暴死了,I题很友好但没来得及碰。

眼睁睁地看着自己从rank2掉到rank5,太窝囊了x

lyd大佬tql,看一个切一个,稳得不行。

接下来的六天继续加油(只是题解大概不会这么详细了x

posted @ 2019-01-13 00:41  Armeria  阅读(128)  评论(0编辑  收藏  举报