zoukankan      html  css  js  c++  java
  • 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
    ", 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} - xsum{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
    ", 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
    ", 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
    ", 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<{ m 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
    ", max(get_weekness(l, 1), get_weekness(l, -1)));
    	return 0;
    }
    
    

    G

    Description

    一条长(L)的河上, 除了({ m START})({ m 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
    ", 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)。最简洁的判断方法是,用类似借教室的前缀和技巧。

    代码中用({ m add})表示增减标记,({ m 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
    ", 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
    ", ans1, ans2);
    	}
    	
    	return 0;
    }
    
    

    说点什么

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

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

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

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

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

  • 相关阅读:
    ABP之模块分析
    AutoMapper之ABP项目中的使用介绍
    Castle Windsor常用介绍以及其在ABP项目的应用介绍
    EasyUI实战经验总结,给有需要的人
    无法发送具有此谓词类型的内容正文
    ADO.NET 新特性之SqlBulkCopy
    SVN无法Cleanup
    Mac使用操作
    Mac下的Mysql无法登陆的问题
    mac 终端 常用命令
  • 原文地址:https://www.cnblogs.com/yearwhk/p/10261697.html
Copyright © 2011-2022 走看看