inlinedoublefx(double x){ return ((a * x + b) * x + c) * x + d; }
inlineintsign(double x){ if (x > 0.01) return1; if (x < -0.01) return-1; return0; }
// 查找区间内一个零点 inlinedoublefind(double left, double right){ int l = sign(fx(left)), r = sign(fx(right)); while (right - left >= 0.01) { double mid = (left + right) / 2; int m = sign(fx(mid)); if (l + m == 0) right = mid; elseif (r + m == 0) left = mid; else return mid; } return left; }
intmain(){ scanf("%lf%lf%lf%lf", &a, &b, &c, &d); // 在 [-100,100] 枚举长度为 1 的区间 int aa = sign(fx(-101)), bb = sign(fx(-100)), i = 0; for (double l = -101; l < 101; l += 1) { if (aa == 0) { ans[i++] = l; if (i == 3) break; } if (aa + bb == 0 && aa - bb != 0) { ans[i++] = find(l, l + 1); if (i == 3) break; } aa = bb; bb = sign(fx(l + 2)); } for (int i = 0; i < 3; ++i) { printf("%.2f ", ans[i]); } return0; }
P2678 [NOIP2015 提高组]
跳石头
题目描述
一年一度的“跳石头”比赛又要开始了!
这项比赛将在一条笔直的河道中进行,河道中分布着一些巨大岩石。组委会已经选择好了两块岩石作为比赛起点和终点。在起点和终点之间,有
N
块岩石(不含起点和终点的岩石)。在比赛过程中,选手们将从起点出发,每一步跳向相邻的岩石,直至到达终点。
为了提高比赛难度,组委会计划移走一些岩石,使得选手们在比赛过程中的最短跳跃距离尽可能长。由于预算限制,组委会至多从起点和终点之间移走
M
块岩石(不能移走起点和终点的岩石)。
输入格式
第一行包含三个整数 L, N, M,分别表示起点到终点的距离,起点和终点之间的岩石数,以及组委会至多移走的岩石数。保证
L ≥ 1 且 N ≥ M ≥ 0。
接下来 N
行,每行一个整数,第 i
行的整数 Di(0 < Di < L),
表示第 i
块岩石与起点的距离。这些岩石按与起点距离从小到大的顺序给出,且不会有两个岩石出现在同一个位置。
defcheck(st, jump): """ 给出跳跃距离,返回移除石头数量 """ pre, cnt = 0, 0 for a in st: if a - pre < jump: cnt += 1 else: pre = a return cnt
defmain(): dist, n, m = map(int, input().split()) st = [int(input()) for _ inrange(n)] st.append(dist) left, right = 0, int(2e9) while left < right: mid = (left + right + 1) >> 1 if check(st, mid) <= m: left = mid else: right = mid - 1 print(left) main()
intcheck(int m, int jump){ int pre = 0, cnt = 0; for (int i = 0; i < m; ++i) { if (arr[i] - pre < jump) ++cnt; else pre = arr[i]; } return cnt; }
intmain(){ int dist, n, m; scanf("%d%d%d", &dist, &n, &m); for (int i = 0; i < n; ++i) { scanf("%d", &arr[i]); } arr[n] = dist; int left = 0, right = 2000000000; while (left < right) { int mid = left + ((right - left + 1) >> 1); if (check(n + 1, mid) <= m) left = mid; else right = mid - 1; } printf("%d", left); return0; }
Scannersc=newScanner(System.in); intdist= sc.nextInt(), n = sc.nextInt(), m = sc.nextInt(); int[] st = newint[n + 1]; for (inti=0; i < n; ++i) { st[i] = sc.nextInt(); } st[n] = dist; intleft=0, right = 2000000000; while (left < right) { intmid= left + (right - left + 1 >> 1); if (check(st, mid) <= m) left = mid; else right = mid - 1; } System.out.println(left);
sc.close(); }
publicintcheck(int[] st, int jump) { intpre=0, cnt = 0; for (int a : st) { if (a - pre < jump) ++cnt; else pre = a; } return cnt; } }
迷阵由 n × m
个相同的小房间组成,每个房间与相邻四个房间之间有门可通行。在第 n 行的 m 个房间里有 m
个机关,这些机关必须全部打开才可以进入大使馆。而第 1 行的 m 个房间有 m
扇向外打开的门,是迷阵的入口。除了第 1
行和第 n
行的房间外,每个房间都被使馆的安保人员安装了激光杀伤装置,将会对进入房间的人造成一定的伤害。第
i 行第 j 列 造成的伤害值为 pi, j(第
1 行和第 n 行的 p 值全部为 0)。
现在某组织打算以最小伤害代价进入迷阵,打开全部机关,显然,他们可以选
择任意多的人从任意的门进入,但必须到达第 n
行的每个房间。一个士兵受到的伤害值为他到达某个机关的路径上所有房间的伤害值中的最大值,整个部队受到的伤害值为所有士兵的伤害值中的最大值。现在,这个恐怖组织掌握了迷阵的情况,他们需要提前知道怎么安排士兵的行进路线可以使得整个部队的伤害值最小。
defdfs(mat, cur: int, vis: set, i: int, j: int): if mat[i][j] > cur: returnFalse n, m = len(mat), len(mat[0]) if i == n - 1: returnTrue vis.add(xy2int(i, j)) for dx, dy indir: x, y = i + dx, j + dy if x < 0or x >= n or y < 0or y >= m: continue key = xy2int(x, y) if key notin vis and dfs(mat, cur, vis, x, y): returnTrue returnFalse
defmain(): n, m = map(int, input().split()) mat = [list(map(int, input().split())) for _ inrange(n)]
left, right = min(min(a) for a in mat), max(max(a) for a in mat) while left < right: mid = (left + right) >> 1 if check(mat, mid): right = mid else: left = mid + 1 print(left)
const int N = 1007; int mat[N][N], vis[N * N]; int n, m; int dir_x[]{1, 0, 0, -1}, dir_y[]{0, 1, -1, 0};
int xy2int(int x, int y) { return x * N + y; }
bool dfs(int i, int j, int cur) { if (mat[i][j] > cur) return false; if (i == n - 1) return true; vis[xy2int(i, j)] = 1; for (int k = 0; k < 4; ++k) { int dx = dir_x[k], dy = dir_y[k]; int x = i + dx, y = j + dy; if (x < 0 || x >= n || y < 0 || y >= m || mat[x][y] > cur || vis[xy2int(x, y)] == 1) continue; if (dfs(x, y, cur)) return true; } return false; }
int main() { int left = 0, right = N * N, mid; scanf("%d%d", &n, &m); for (int i = 0; i < n; ++i) { for (int j = 0; j < m; ++j) { scanf("%d", &mat[i][j]); right = right < mat[i][j] ? mat[i][j] : right; } } while (left < right) { mid = (left + right) >> 1; if (check(mid)) right = mid; else left = mid + 1; } printf("%d", left); return0; }
P1314 [NOIP2011 提高组]
聪明的质监员
题目描述
小T
是一名质量监督员,最近负责检验一批矿产的质量。这批矿产共有 n 个矿石,从 1 到 n 逐一编号,每个矿石都有自己的重量
wi
以及价值 vi
。检验矿产的流程是:
constint MAX_VAL = 200007; int n, m, x, y; longlong s; int w[MAX_VAL], v[MAX_VAL], pleft[MAX_VAL], pright[MAX_VAL]; longlong pre_n[MAX_VAL], pre_v[MAX_VAL];
longlongcheck(int pw){ // 计算校验和 longlong ans = 0; // 先预处理出前缀数组; pre_n[0] = 0; pre_v[0] = 0; for (int i = 0; i < n; ++i) { pre_n[i + 1] = pre_n[i] + (w[i] >= pw ? 1 : 0); pre_v[i + 1] = pre_v[i] + (w[i] >= pw ? v[i] : 0); } // 对每个区间计算 for (int i = 0; i < m; ++i) { int l = pleft[i] - 1, r = pright[i] - 1; ans += (pre_n[r + 1] - pre_n[l]) * (pre_v[r + 1] - pre_v[l]); } return ans; }
intmain(){
// redir();
scanf("%d%d%lld", &n, &m, &s); // 读数据 x = 1000007; y = 0; for (int i = 0; i < n; ++i) { scanf("%d%d", &w[i], &v[i]); x = min(x, w[i]); y = max(y, w[i]); } for (int i = 0; i < m; ++i) { scanf("%d%d", &pleft[i], &pright[i]); } // 二分 W longlong y1, y2; int l = x - 1, r = y + 1; // 先查找不小于 s 的最小值 while (l < r) { int mid = (l + r + 1) >> 1; if (check(mid) >= s) l = mid; else r = mid - 1; } y1 = check(l); // 再查找不大于 s 的最大值 for (r = y + 1; l < r; ) { int mid = (l + r) >> 1; if (check(mid) > s) l = mid + 1; else r = mid; } y2 = check(l); printf("%lld\n", min(y1 - s, s - y2)); return0; }
constint N = 1000007; int n, m, r; ll room[N], diff[N], d[N], s[N], t[N];
boolcheck(int day){ memset(diff, 0, sizeof(diff)); for (int i = 0; i < day; ++i) { diff[s[i] - 1] += d[i]; diff[t[i]] -= d[i]; } ll cnt = 0; for (int i = 0; i < n; ++i) { cnt += diff[i]; if (cnt > room[i]) returnfalse; } returntrue; }
intmain(){
scanf("%d%d", &n, &m); for (int i = 0; i < n; ++i) { scanf("%lld", &room[i]); } for (int i = 0; i < m; ++i) { scanf("%lld%lld%lld", &d[i], &s[i], &t[i]); } int left = 1, right = m + 1; while (left < right) { int mid = (left + right) >> 1; if (check(mid)) left = mid + 1; else right = mid; } if (left == m + 1) printf("0"); elseprintf("-1\n%d", left); return0; }
P4343 [SHOI2015]自动刷题机
题目背景
曾经发明了信号增幅仪的发明家 SHTSC
又公开了他的新发明:自动刷题机——一种可以自动 AC 题目的神秘装置。
1.写了 x 行代码
2.心情不好,删掉了之前写的 y
行代码。(如果 y
大于当前代码长度则相当于全部删除。)
对于一个 OJ,存在某个固定的正整数长度 n,一旦自动刷题机在某秒结束时积累了大于等于
n 行的代码,它就会自动提交并
AC 此题,然后新建一个文件(即弃置之前的所有代码)并开始写下一题。SHTSC
在某个 OJ
上跑了一天的自动刷题机,得到了很多条关于写代码的日志信息。他突然发现自己没有记录这个
OJ 的 n
究竟是多少。所幸他通过自己在 OJ 上的 Rank 知道了自动刷题机一共切了 k 道题,希望你计算 n 可能的最小值和最大值。
输入格式
第一行两个整数 l, k,表示刷题机的日志一共有
l 行,一共了切了 k 题。
接下来 l 行,每行一个整数
xi,依次表示每条日志。若
xi ≥ 0,则表示写了
xi
行代码,若 xi < 0,则表示删除了
−xi
行代码。
输出格式
输出一行两个整数,分别表示 n 可能的最小值和最大值。
如果这样的 n
不存在,请输出一行一个整数 −1。
样例 #1
样例输入 #1
1 2 3 4 5
4 2 2 5 -3 9
样例输出 #1
1
3 7
提示
数据规模与约定
对于 20% 的数据,保证 l ≤ 10;
对于 40% 的数据,保证 l ≤ 100 ;
对于 60% 的数据,保证l ≤ 2 × 103;
对于 100% 的数据,保证 1 ≤ l ≤ 105,−109 ≤ xi ≤ 109。
解题思路
显然,想要直接推断 n
的值是困难的,但如果知道 n
求切题次数 k 却很简单。
另一方面,显然有 n
越大,k
越小,因为题目要求代码越多,那么满足代码数量 ≥ n 的时刻必然越少,k 也就越小。
结合上述两点结论,使用二分法也就是自然而然的了
先用二分法求最小值,然后再二分求最大值,很常规的二分操作了,接下来我们考虑
n
的取值范围是什么,毫无疑问最小值是
1,而最大值应该是代码中可能的最大行数,此时 k 可能取得最小值 1,题目没说 k 的取值范围,但 k 不可能是 0,因为 k 为 0那么 n 没有上界。
#define ll long long constint N = 100007; int length, k; ll code[N];
intcheck(ll w){ // 计算切题数 int cnt = 0; ll pre = 0; for (int i = 0; i < length; ++i) { pre = max(0ll, pre + code[i]); if (pre >= w) { ++cnt; pre = 0; } } return cnt; }
intmain(){ memset(code, 0, sizeof(code)); scanf("%d%d", &length, &k); // 读入数据同时测测 n 的上界 ll bound = 0, cnt = 0; for (int i = 0; i < length; ++i) { scanf("%lld", &code[i]); cnt = max(0ll, cnt + code[i]); bound = max(cnt, bound); } // 第一次先搜索 n 的最小值 ll left = 1, right = bound; while (left < right) { ll mid = (left + right) >> 1; // 注意 n 越小 k 越大,所以这里要增大 n if (check(mid) > k) left = mid + 1; else right = mid; } if (check(left) != k) { printf("-1"); return0; } printf("%lld", left); // 接下来搜索 n 的最大值 for (right = bound; left < right; ) { ll mid = (left + right + 1) >> 1; if (check(mid) < k) right = mid - 1; else left = mid; } printf(" %lld", left); return0; }