• 二分与三分


    前言

    最近做的二分与三分的题目

    做的好像都是板子 不是板子的题目用一种 很诡异 很巧妙的方法水了过去


    1. 愤怒的牛

    题意简化:

    m头牛放入一条直线上的n间牛舍 最大化最小距离

    思路:

    二分板子

    code:

    /*
      Time: 12.20
      Worker: Blank_space
      Source: #10011. 「一本通 1.2 例 1」愤怒的牛
    */
    /*--------------------------------------------*/
    #include<cstdio>
    #include<algorithm>
    using namespace std;
    /*--------------------------------------头文件*/
    const int A = 1e4 + 7;
    const int B = 1e5 + 7;
    const int C = 1e6 + 7;
    const int D = 1e7 + 7;
    const int mod = 1e9 + 7;
    const int INF = 0x3f3f3f3f;
    const int FFF = 0x8fffffff;
    /*------------------------------------常量定义*/
    int n, m, a[B], c[B], ans;
    /*------------------------------------变量定义*/
    inline int read() {
    	int x = 0, f = 1; char ch = getchar();
    	while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
    	while(ch >= '0' && ch <= '9') {x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
    	return x * f;
    }
    /*----------------------------------------快读*/
    bool check(int x)
    {
    	int sum = 0, num = 1;
    	for(int i = 2; i <= n; i++)
    	{
    		if(sum + c[i] < x) sum += c[i];
    		else {sum = 0; num++;}
    	}
    	return num >= m;
    }
    /*----------------------------------------函数*/
    int main()
    {
        n = read(); m = read(); int l = 0, r = 0; 
        for(int i = 1; i <= n; i++) a[i] = read(), r = max(r, a[i]);
        sort(a + 1, a + 1 + n);
        for(int i = 1; i <= n; i++) c[i] = a[i] - a[i - 1];
        while(l <= r)
        {
        	int mid = (l + r) >> 1;
        	if(check(mid))
        	{
        		ans = mid;
        		l = mid + 1;
        	}
        	else r = mid - 1;
        }
        printf("%d", ans);
    	return 0;
    }
    

    2. Best Cow Fences

    题意简化:

    给定一个长度为 (n) 的非负整数序列(A) ,求一个平均数最大的,长度不小于 (L)的子段

    思路:

    二分平均数 将原序列中所有的数减去这个平均数 转换为判断一个子段非负 也可以说是求最大子段和

    枚举到第 i 个数时 用第 i 数减去 第 1 ~ i - L 个数中最小值 统计最大值

    因为已经减去了平均值 所以最后判断这个最大子段和是否非负

    code:

    /*
      Time: 12.20
      Worker: Blank_space
      Source: #10012. 「一本通 1.2 例 2」Best Cow Fences
    */
    /*--------------------------------------------*/
    #include<cstdio>
    #include<algorithm>
    using namespace std;
    /*--------------------------------------头文件*/
    const int A = 1e4 + 7;
    const int B = 1e5 + 7;
    const int C = 1e6 + 7;
    const int D = 1e7 + 7;
    const int mod = 1e9 + 7;
    const int INF = 0x3f3f3f3f;
    const int FFF = 0x8fffffff;
    /*------------------------------------常量定义*/
    int n, L;
    double a[B], b[B], sum[B];
    /*------------------------------------变量定义*/
    inline int read() {
    	int x = 0, f = 1; char ch = getchar();
    	while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
    	while(ch >= '0' && ch <= '9') {x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
    	return x * f;
    }
    /*----------------------------------------快读*/
    bool check(double x)
    {
    	double ans = -INF, minn = INF;
    	for(int i = 1; i <= n; i++) b[i] = a[i] - x, sum[i] = sum[i - 1] + b[i];
    	for(int i = L; i <= n; i++)
    	{
    		minn = min(minn, sum[i - L]);
    		ans = max(ans, sum[i] - minn);
    	}
    	return ans > 0;
    }
    /*----------------------------------------函数*/
    int main()
    {
        n = read(); L = read();
        for(int i = 1; i <= n; i++) a[i] = read();
        double eps = 1e-5, l = -1e6, r = 1e6;
        while(r - l > eps)
        {
        	double mid = (l + r) / 2;
        	if(check(mid)) l = mid;
        	else r = mid;
        }
        printf("%d", int(r * 1e3));
    	return 0;
    }
    

    3. 曲线

    题意简化:

    求n个二次函数在区间[1, n]上的最大值

    思路:

    三分板子

    code:

    /*
      Time: 12.20
      Worker: Blank_space
      Source: #10013. 「一本通 1.2 例 3」曲线
    */
    /*--------------------------------------------*/
    #include<cstdio>
    #include<algorithm>
    #include<string.h>
    #define emm(x) memset(x, 0, sizeof x)
    using namespace std;
    /*--------------------------------------头文件*/
    const int A = 1e4 + 7;
    const int B = 1e5 + 7;
    const int C = 1e6 + 7;
    const int D = 1e7 + 7;
    const int mod = 1e9 + 7;
    const int INF = 0x3f3f3f3f;
    const int FFF = 0x8fffffff;
    /*------------------------------------常量定义*/
    int T, n;
    struct node {
    	double a, b, c;
    }a[B];
    /*------------------------------------变量定义*/
    inline int read() {
    	int x = 0, f = 1; char ch = getchar();
    	while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
    	while(ch >= '0' && ch <= '9') {x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
    	return x * f;
    }
    /*----------------------------------------快读*/
    double f(double x)
    {
    	double _ans = -INF;
    	for(int i = 1; i <= n; i++) _ans = max(_ans, a[i].a * x * x + a[i].b * x + a[i].c);
    	return _ans;
    }
    void work()
    {
    	n = read(); emm(a); double l = 0, r = 1000;
    	for(int i = 1; i <= n; i++) {a[i].a = read(); a[i].b = read(); a[i].c = read();}
    	while(l + 1e-10 < r)
    	{
    		double m1 = l + (r - l) / 3, m2 = r - (r - l) / 3;
    		if(f(m1) > f(m2)) l = m1;
    		else r = m2;
    	}
    	printf("%.4f
    ", f(l));
    }
    /*----------------------------------------函数*/
    int main()
    {
        T = read();
        while(T--) work();
    	return 0;
    }
    

    4. 数列分段II

    题意简化:

    长度为(n)的正整数数列 分为连续m段 最小化每一段的最大值

    思路:

    二分每一区间的最大值 进行检查

    二分答案板子

    code:

    /*
      Time: 12.20
      Worker: Blank_space
      Source: #10014. 「一本通 1.2 练习 1」数列分段 II
    */
    /*--------------------------------------------*/
    #include<cstdio>
    #include<algorithm>
    using namespace std;
    /*--------------------------------------头文件*/
    const int A = 1e4 + 7;
    const int B = 1e5 + 7;
    const int C = 1e6 + 7;
    const int D = 1e7 + 7;
    const int mod = 1e9 + 7;
    const int INF = 0x3f3f3f3f;
    const int FFF = 0x8fffffff;
    /*------------------------------------常量定义*/
    int n, m, a[B], s, ans, maxx;
    /*------------------------------------变量定义*/
    inline int read() {
    	int x = 0, f = 1; char ch = getchar();
    	while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
    	while(ch >= '0' && ch <= '9') {x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
    	return x * f;
    }
    /*----------------------------------------快读*/
    bool check(int x)
    {
    	int sum = 0, num = 1;
    	for(int i = 1; i <= n; i++)
    		if(sum + a[i] <= x) sum += a[i];
    		else {sum = a[i]; num++;}
    	return num <= m;
    }
    /*----------------------------------------函数*/
    int main()
    {
    //	freopen("divide_b4.in", "r", stdin);
        n = read(); m = read();
        for(int i = 1; i <= n; i++) a[i] = read(), s += a[i], maxx = max(a[i], maxx);
        int l = maxx, r = s;
        while(l <= r)
        {
        	int mid = (l + r) >> 1;
        	if(check(mid))
        	{
        		ans = mid;
        		r = mid - 1;
        	}
        	else l = mid + 1;
        }
        printf("%d", ans);
    	return 0;
    }
    

    5. 扩散

    题意简化:

    在一个二维空间内 给定 n 个点 一个点每过一个单位的时间就会向4个方向扩散一个距离 两个点的扩散区域有重合时称两个点联通 求n个点形成一个连通块时的最小时间

    思路:

    当两个点中间没有其他点时 这两个点联通的最小时间就是这两个点的曼哈顿距离除以二

    由此来判断二分出的时间是否合法

    对于点与点之间的联通 采用并查集来实现

    code:

    /*
      Time: 12.20
      Worker: Blank_space
      Source: #10015. 「一本通 1.2 练习 2」扩散
    */
    /*--------------------------------------------*/
    #include<cstdio>
    #include<algorithm>
    using namespace std;
    /*--------------------------------------头文件*/
    const int A = 1e4 + 7;
    const int B = 1e5 + 7;
    const int C = 1e6 + 7;
    const int D = 1e7 + 7;
    const int mod = 1e9 + 7;
    const int INF = 0x3f3f3f3f;
    const int FFF = 0x8fffffff;
    /*------------------------------------常量定义*/
    int n, maxx, maxy, minx = INF, miny = INF, fa[100], ans, cnt;
    struct node {
    	int x, y;
    }a[100];
    /*------------------------------------变量定义*/
    inline int read() {
    	int x = 0, f = 1; char ch = getchar();
    	while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
    	while(ch >= '0' && ch <= '9') {x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
    	return x * f;
    }
    /*----------------------------------------快读*/
    int find(int x) { if(fa[x] != x) return fa[x] = find(fa[x]); return x;}
    bool check(int x)
    {
    	for(int i = 1; i <= n; i++) fa[i] = i; cnt = 0;
    	for(int i = 1; i <= n; i++)
    		for(int j = i + 1; j <= n; j++)
    			if(abs(a[i].x - a[j].x) + abs(a[i].y - a[j].y) <= x * 2)
    			{
    				int xx = find(i), yy = find(j);
    				if(xx != yy) fa[xx] = yy;
    			}
    	for(int i = 1; i <= n; i++)
    		if(fa[i] == i) cnt++;
    	return cnt == 1;
    }
    /*----------------------------------------函数*/
    int main()
    {
        n = read();
        for(int i = 1; i <= n; i++)
    	{
    		a[i].x = read();a[i].y = read();
    		maxx = max(maxx, a[i].x); maxy = max(maxy, a[i].y);
    		minx = min(minx, a[i].x); miny = min(miny, a[i].y);
    	}
        int l = 0, r = maxx + maxy - minx - miny;
        while(l <= r)
        {
        	int mid = (l + r) >> 1;
        	if(check(mid))
        	{
        		ans = mid;
        		r = mid - 1;
        	}
        	else l = mid + 1;
        }
        printf("%d", ans);
    	return 0;
    }
    

    6. 灯泡

    题目描述

    相比 wildleopard 的家,他的弟弟 mildleopard 比较穷。他的房子是狭窄的而且在他的房间里面仅有一个灯泡。每天晚上,他徘徊在自己狭小的房子里,思考如何赚更多的钱。有一天,他发现他的影子的长度随着他在灯泡和墙壁之间走到时发生着变化。一个突然的想法出现在脑海里,他想知道他的影子的最大长度。

    bulb.jpg

    思路:

    我感觉这是个结论题

    这是什么二分啊 这不就是一个数学题吗

    所以用一种很 诡异 巧妙的方法水过

    但我的复杂度是 (O(1))

    首先 根据题目:

    灯高 H

    人高 h

    灯距墙壁的距离为 D

    人的影子长度为 L

    考虑表示人的影子长度

    设:

    人与灯的距离为 x

    影子落在墙上(如果有的话)的部分为 y

    墙上的那部分影子如果再落在地上所能产生的影子的长度为 t

    如图:

    (下面的推导过程我建议把图画下来对着图理解)

    然后 根据影子是否落在墙上分类讨论

    一: 影子没有落在墙上

    此时 根据相似三角形可以直接得出:

    [frac{h}{H} = frac{L}{x + L} ]

    化简 得:

    [L = frac{xh}{H - h} ]

    这个时候 因为要保证影子不会落在墙上 所以 有:

    [x + L leq D ]

    将 L 代入 得:

    [x + frac{xh}{H - h} leq D ]

    化简 得:

    [x leq frac{(H - h)D}{H} ]

    所以 x 的范围:

    [0leq x leq frac{(H - h)D}{H} ]

    二: 影子有一部分落在墙上

    由图 我们不难看出:

    [L = D - x + y ]

    此时 根据相似三角形 有:

    [frac{D + t}{H} = frac{L - y + t}{h} = frac{t}{y} ]

    设出的为自变量 想办法让 x 进入这个式子

    观察一下图 可以列出:

    [L - y + t = D - x + t ]

    所以上面的式子又可以化为:

    [frac{D + t}{H} = frac{D - x + t}{h} = frac{t}{y} ]

    将式子拆开来看

    首先是左边两个:

    [frac{D + t}{H} = frac{D - x + t}{h} ]

    化简 得:

    [t = frac{DH - xH - hD}{h - H} ]

    我们就有了 t 与 x 的关系

    再看右边:

    [frac{D - x + t}{h} = frac{t}{y} ]

    化简 得:

    [y = frac{ht}{D - x + t} ]

    然后将 t 代入 得:

    [y = frac{hfrac{DH - xH - hD}{h - H}}{D - x + frac{DH - xH - hD}{h - H}} ]

    化简 得 :

    [y = frac{hD + xH - DH}{x} ]

    x 与 y 的关系也有了

    回到上面的式子:

    [L = D - x + y ]

    代入 y 得:

    [L = D - x + frac{hD + xH - DH}{x} ]

    化简 得:

    [L = D - x + H + frac{D(h - H)}{x} ]

    结合上一种情况 此时 x 的范围为:

    [frac{(H - h)D}{H} < x leq D ]

    整理这两种情况 就有了L 关于 x 的函数:

    [L = egin{cases}frac{xh}{H - h},0leq x leq frac{(H - h)D}{H}\ D - x + H + frac{D(h - H)}{x},frac{(H - h)D}{H} < x leq D end{cases} ]

    所以有什么用呢?

    上面的那个式子 很明显是单调递增的 所以最大值在右端点取到

    下面的那个式子 再观察一下:

    [L = D - x + H + frac{D(h - H)}{x} = D + H - (x + frac{D(H - h)}{x}) ]

    这就很明显了

    后面那一坨东西就是对勾函数

    那么:

    [x + frac{D(H - h)}{x}geq 2sqrt{D(H - h)} ]

    所以:

    [L = D - H - (x + frac{D(H - h)}{x})\ leq D + H - 2sqrt{D(H - h)} ]

    当且仅当:

    [x = sqrt{D(H - h)} ]

    时 L 取到最大值

    这时我们发现 这个 x 到底能不能取到是一个问题

    对于这一问题 我们再次分类讨论

    一:

    [sqrt{D(H - h)}leq frac{(H - h)D}{H} ]

    因为前面的那个式子是单调的

    所以此时的答案就为:

    [L= frac{xh}{H - h} = frac{H - h}{D} frac{h}{H - h} = frac{hD}{H} ]

    二:

    [frac{(H - h)D}{H}<sqrt{D(H - h)} leq D ]

    将两个取值范围的最值进行比较 取较大值

    即 比较:

    [D + H - 2sqrt{D(H - h)} ext{与}frac{hD}{H} ]

    的大小

    三:

    [sqrt{D(H - h)} > D ]

    此时 ((0 , D]) 这一区间内 函数是单调递增的

    所以最大值在 (x = D) 处取到

    此时 最大值为:(h)

    再与(frac{hD}{H}) 进行比较即可

    至此 对于每一次的询问 则可以 (O(1)) 的进行回答

    code:

    /*
      Time: 12.20
      Worker: Blank_space
      Source: #10016. 「一本通 1.2 练习 3」灯泡
      要什么二分三分的 这就是个结论题 
    */
    /*--------------------------------------------*/
    #include<cstdio>
    #include<iostream>
    #include<algorithm>
    #include<cmath>
    using namespace std;
    /*--------------------------------------头文件*/
    const int A = 1e4 + 7;
    const int B = 1e5 + 7;
    const int C = 1e6 + 7;
    const int mod = 1e9 + 7;
    const int INF = 0x3f3f3f3f;
    const int FFF = 0x8fffffff;
    /*------------------------------------常量定义*/
    int T;
    double H, h, D;
    /*------------------------------------变量定义*/
    void work()
    {
    	cin >> H >> h >> D;
    	printf("%.3f
    ", (sqrt(D * (H - h)) < (H - h) * D / H) ? (D * h / H) : (max(h * D / H, (sqrt(D * (H - h)) > D) ? h : (D + H - 2 * sqrt(D * (H - h))))));
    }
    /*----------------------------------------函数*/
    int main()
    {
        scanf("%d", &T);
        while(T--) work();
    	return 0;
    }
    
    
  • 相关阅读:
    VC++ 常用数学函数
    Skin++的使用 (成功在vs2008试过)
    解决switch使用String做参数 (jdk1.7新增),更改jdk版本后报错的问题
    [转]jdk1.7中 新增 switch 使用String 做参数 的实现
    [转]网页版Firebug
    [转]Eclipse导入工程后,XDoclet错误:Missing library: xdoclet1.2.1.jar. Select the home directory for XDoclet. 1.
    SAP related blogs
    [转]SAP FI/CO 模块设置
    如何调用BADI
    [转]一则关于ABAP程序员的趣谈
  • 原文地址:https://www.cnblogs.com/blank-space-/p/14164945.html
Copyright © 2020-2023  润新知