zoukankan      html  css  js  c++  java
  • 【jzoj 7207】缘木求鱼(数论)(高精)

    缘木求鱼

    题目链接:jzoj 7207

    题目大意

    定义 f(x) 函数为开一个数组大小为 x 的线段树,它的最大下标。
    要你求 l~r 范围内 f(x)/x 的最大值。

    思路

    首先几个东西要知道。

    1. 线段树一个点左子树大小最多比右子树大小大一。
    2. 深度大的点比深度小的点编号大,如果相同深度,右边的点大。
    3. 大小为 x 的线段树的深度是 (leftlceillog_2^x ight ceil)

    那首先我们想如何求 (f(x))
    首先确定最终方向,由于线段树是有关二进制的,而且数据范围是二进制给入,还是几十万位,所以我们是要从它的二进制上来找一些结论。

    首先是最暴力的,从 (1) 开始一步一步走,那我们考虑能不能直接确定最优的在那一边。
    (假设当前区间长度为 (x),左儿子区间长度为 (x0=leftlfloordfrac{x+1}{2} ight floor),右儿子区间长度为 (x1=leftlfloordfrac{x}{2} ight floor)
    然后结合上面的第二条,我们可以得出当且仅当 (leftlceillog_2^{x0} ight ceil>leftlceillog_2^{x1} ight ceil) 时,最优值才会在左边。而且根据第一条,这个时候一定会有 (x=2^k+1(kgeqslant1))

    然后你会发现在出现最高的两个 (1) 之前都是往右走,不会满足 (2^k+1) 条件。
    然后到了之后,因为一直在满足(除了最后一步),所以就是一直左走,最后右走。

    那我们发现 (f(x)) 只跟最高位的两个 (1) 有关系,如果设分别是 (2^p,2^q(p>q)),我们还可以得出:
    (f(x)=(2^{q+1}-1)*2^{p-q}*2+1=2^{p+2}-2^{p-q+1}+1)

    但是 (x) 不一定有两个 (1) 啊,如果只有一个 (1) 呢?
    显然,那就是一直右走,设为 (2^p),那就是:
    (f(x)=2^{p+1}-1)

    然后我们就可以把第三档的部分分拿了。


    接下来,我们才可以开始这题的第二档暴力。(笑死)

    我们枚举 (p,q)(q) 可能无),然后剩下的低位在 ([l,r]) 的前提下尽可能的小。

    但是这时候又有问题了,我们要比较的是 (frac{f(x)}{x}),做高精度小数除法是在想什么。

    所以我们就考虑比较当前最优答案和当前要更新的答案。
    (frac{f(x)}{x}<frac{f(y)}{y})
    (f(x)y<f(y)x)

    然后由于 (f(x)) 只有两项或者三项,我们可以直接将其看做把 x / y 的二进制的两个 / 三个平移操作得到的二进制加起来,那就只有高精加和高精减了。

    那计算 (O(n)),枚举 (p,q)(O(n^2)),总的就是 (O(n^3)),我们就能过掉前三档啦。


    然后就是最后一档啦,那我们就来慢慢的优化吧。

    然后由于 (1sim 2^i) 的答案会比 (1sim 2^{i-1}) 的优,我们可以限制 (max{n,m-1}leqslant pleqslant m),然后就变成 (O(n^2)) 的。

    然后小小的证明:
    你多了一层,你的点最大位置肯定会至少 (*2),那你数是最大乘了 (*2),所以结果只可能变大,不可能变小。

    但是还是木大啊。


    我们考虑减少决策点((q)) 的选择。

    首先我们保证 (l,r) 二进制位数相同,如果不同,我们可以把它分成两部分(前面说了位数可以变成至多只差一位),然后两个的答案取最优的。

    然后我们把 (l,r) 用二进制表示出来。
    (l=10...01...)
    (r=10...10...)

    那如果这里搞 LCP,LCP 里面有超过一个 (1),那第二个 (1) 的范围就直接限死了,就直接是 (l)
    那否则我们看第二个 (1) 放在哪里。

    那假设两个的第二个一分别是 (u,v)。((r) 找到的是 (v)(l) 找到的是 (u)(uleqslant v)
    那我们的 (q) 就只能在 ([u,v]) 中,如果是 (u),那就是 (l),放在其它位置(((u,v]))的时候,后面的位都放 (0)

    那除了 (u),别的都可以以 (x=2^p+2^q) 的形式表示出来。
    可以通过打表找规律和进行导数练习得到 (p) 固定的时候,(q) 接近 (p/2) 最优。
    (你也可以自己用电脑画个图像看看)

    然后你的决策点就变得很少。
    (注意一些左端点在 (p/2) 右边,就只能选左端点;右端点在 (p/2) 左边,就只能选右端点)
    (然后为了以防万一,我们会把 (l) 的答案也算上)
    然后各种情况处理一下计算一下就好了。

    然后决策点就只剩下两三个左右,就变成 (O(n)) 可以过啦。

    代码

    #include<cstdio>
    #include<cstring>
    #include<iostream>
    #include<algorithm>
    
    using namespace std;
    
    int n, m, ansn;
    char L[2000001], R[2000001], mid[2000001];
    int ans[2000001], ans_[2000001], tmp[2000001];
    int x_[4000001], y_[4000001], opx[4], opy[4];
    int degx[4], degy[4], xn, yn;
    
    void jia(int *x, int *y, int deg) {
    	int len = deg + y[0], w = 0;
    	for (int i = deg + 1; i <= len; i++) {
    		x[i] += w + y[i - deg];
    		w = x[i] / 2;
    		x[i] %= 2;
    	}
    	while (w) {
    		x[++len] = w;
    		w = x[len] / 2;
    		x[len] /= 2;
    	}
    	if (x[0] < len) x[0] = len;
    }
    
    void jian(int *x, int *y, int deg) {
    	int len = deg + y[0], w = 0;
    	for (int i = deg + 1; i <= len; i++) {
    		x[i] -= w + y[i - deg];
    		w = (x[i] < 0);
    		x[i] &= 1;
    	}
    	while (w) {
    		x[++len] -= w;
    		w = (x[len] < 0);
    		x[len] &= 1;
    	}
    	while (x[0] > 0 && !x[x[0]]) x[0]--;
    }
    
    void get_good(int *x, int *y) {
    	if (x[0] == y[0]) {//相同 
    		bool same = 1;
    		for (int i = 1; i <= x[0]; i++)
    			if (x[i] != y[i]) {
    				same = 0; break;
    			}
    		if (same) return ;
    	}
    	
    	for (int i = 1; i <= x_[0]; i++) x_[i] = 0;
    	for (int i = 1; i <= y_[0]; i++) y_[i] = 0;
    	x_[0] = y_[0] = 0;
    	
    	xn = yn = 0;
    	int firx = x[0] + 1, firy = y[0] + 1;
    	for (int i = 2; i <= x[0]; i++)
    		if (x[x[0] - i + 1]) {
    			firx = i; break;
    		}
    	for (int i = 2; i <= y[0]; i++)
    		if (y[y[0] - i + 1]) {
    			firy = i; break;
    		}
    	
    	if (firx == x[0] + 1) {//2^0+2^1+...+2^p
    		degx[++xn] = x[0]; opx[xn] = 1;
    		degx[++xn] = 0; opx[xn] = -1;
    	}
    	else {//2^(p+2)-2^(p-q+1)+1
    		degx[++xn] = x[0] + 1; opx[xn] = 1;
    		degx[++xn] = firx; opx[xn] = -1;
    		degx[++xn] = 0; opx[xn] = 1;
    	}
    	if (firy == y[0] + 1) {
    		degy[++yn] = y[0]; opy[yn] = 1;
    		degy[++yn] = 0; opy[yn] = -1;
    	}
    	else {
    		degy[++yn] = y[0] + 1; opy[yn] = 1;
    		degy[++yn] = firy; opy[yn] = -1;
    		degy[++yn] = 0; opy[yn] = 1;
    	}
    	
    	for (int i = 1; i <= xn; i++) {//移位加减
    		if (opx[i] == 1) jia(x_, y, degx[i]);
    			else jian(x_, y, degx[i]);
    	}
    	for (int i = 1; i <= yn; i++) {
    		if (opy[i] == 1) jia(y_, x, degy[i]);
    			else jian(y_, x, degy[i]);
    	}
    	
    	if (x_[0] < y_[0]) {//比较,如果 y_ 优就交换
    		int maxn = max(x[0], y[0]);
    		for (int i = 0; i <= maxn; i++)
    			swap(x[i], y[i]);
    	}
    	else if (x_[0] == y_[0]) {
    		for (int i = x_[0]; i >= 1; i--) {
    			if (x_[i] > y_[i]) return ;
    			if (x_[i] < y_[i]) {
    				int maxn = max(x[0], y[0]);
    				for (int i = 0; i <= maxn; i++)
    					swap(x[i], y[i]);
    				return ;
    			}
    		}
    	}
    }
    
    void get_ans(char *l, char *r, int n) {
    	int now = 1, onenum = 0;
    	while (now <= n && l[now] == r[now]) onenum += (l[now++] == '1');
    	if (onenum > 1) {//lcp 超过 1 个 1
    		for (int i = 1; i <= n; i++)
    			ans[n - i + 1] = l[i] - '0';
    		ans[0] = n;
    		return ;
    	}
    	
    	int lfir = n + 1, rfir = n + 1;
    	for (int i = 2; i <= n; i++)//各自找到第二个 1 
    		if (l[i] == '1') {
    			lfir = i; break;
    		}
    	for (int i = 2; i <= n; i++)
    		if (r[i] == '1') {
    			rfir = i; break;
    		}
    	
    	int p2 = n / 2 + 1;
    	bool ltwo = 0;
    	for (int i = 2; i <= n; i++)
    		if (l[i] == '1') {
    			ltwo = 1; break;
    		}
    	for (int i = 1; i <= n; i++)//先试一下选 l 的 
    		ans[n - i + 1] = l[i] - '0';
    	ans[0] = n;
    	if (rfir <= p2 && p2 <= lfir) {//可以选到 p/2 
    		if (p2 == lfir && ltwo) {//会被 >=l 限制(选l)(前面处理了所以就不用再弄) 
    			if (rfir < lfir && lfir > 2) {//因为会限制所以考虑用两个 1 的方法选后面可以选的那个 
    				for (int i = 1; i <= n; i++)
    					tmp[i] = 0;
    				tmp[n - (p2 - 1) + 1] = tmp[n - 1 + 1] = 1;
    				tmp[0] = n;
    				get_good(ans, tmp);
    			}
    			return ;
    		}
    		for (int i = 1; i <= n; i++)//普通的选 p/2
    			tmp[i] = 0;
    		tmp[n - p2 + 1] = tmp[n - 1 + 1] = 1;
    		tmp[0] = n;
    		get_good(ans, tmp); 
    		return ;
    	}
    	else if (p2 > lfir) {//l 那边超了,限制 l 
    		for (int i = 1; i <= n; i++)
    			tmp[i] = 0;
    		if (ltwo) tmp[n - (lfir - 1) + 1] = 1;
    			else tmp[n - lfir + 1] = 1;
    		tmp[n - 1 + 1] = 1;
    		tmp[0] = n;
    		get_good(ans, tmp); 
    		return ;
    	}
    	else if (rfir > p2) {//r 那边超了 
    		for (int i = 1; i <= n; i++)
    			tmp[i] = 0;
    		tmp[n - rfir + 1] = tmp[n - 1 + 1] = 1;
    		tmp[0] = n;
    		get_good(ans, tmp);
    		return ;
    	}
    }
    
    int main() {
    	scanf("%d %s %d %s", &n, L + 1, &m, R + 1);
    	
    	if (n + 1 < m) {//直接处理最后两位的(1~2^(i+1) 答案比 1~2^i 的优)
    		n = m - 1;
    		L[1] = '1';
    		for (int i = 2; i <= n; i++) L[i] = '0';
    	}
    	
    	if (n + 1 == m) {//要拆成两个相同为的
    		for (int i = 1; i <= n; i++)
    			mid[i] = '1';
    		get_ans(L, mid, n); swap(ans_, ans);
    		mid[1] = '1';
    		for (int i = 2; i <= m; i++)
    			mid[i] = '0';
    		get_ans(mid, R, m); get_good(ans, ans_);
    	}
    	else {//直接做
    		get_ans(L, R, n);
    	}
    	
    	for (int i = 1; i <= ans[0]; i++)//这里倒叙储存,所以要倒叙输出(字符串是正序,数字的是倒叙)
    		printf("%d", ans[ans[0] - i + 1]);
    	
    	return 0;
    }
    
  • 相关阅读:
    Do you want a timeout?
    [整]常用的几种VS编程插件
    [转]Windows的窗口刷新机制
    [整][转]Invoke和BeginInvoke的使用
    [整]C#获得程序路径
    [转]Visual Studio 2010 单元测试目录
    飞秋的实现原理
    面向对象的七大原则
    [转]玩转Google开源C++单元测试框架Google Test系列
    [转]C#中的Monitor类
  • 原文地址:https://www.cnblogs.com/Sakura-TJH/p/jzoj_7207.html
Copyright © 2011-2022 走看看