其他分享
首页 > 其他分享> > 【CF768B】Code For 1 题解

【CF768B】Code For 1 题解

作者:互联网

传送门:B. Code For 1

像我这么菜的 \(Oier\),考场上只想的出来类似模拟线段树的做法啦~

被暴力吊打(大哭)。此题解提供一种新做法做参考。

Solution

1

首先我们发现一个给定的 \(n\),我们是可以求出它最终化为的 01 串的长度。

设 \(s_{lst}\) 为当前分解得到的串的长度,\(x\) 为分解出的、用二进制表示的 01 “串”长度最大的一个数,\(y\) 为长度最大的数的个数。

例如,当输入的 \(n\) 为 10 时,二进制下表示为 1010,此时 \(s_{lst}\) 为 4,\(x\) 为 4, \(y\) 为 1。

将 10 分解成 5,0,5,即二进制下的 101,0,101,此时 \(s_{lst}\) 为 7,\(x\) 为 3,\(y\) 为 2。

进一步分解,2,1,2,0,2,1,2,即二进制下的 10,1,10,0,10,1,10,此时 \(s_{lst}\) 为 11,\(x\) 为 2,\(y\) 为 4。

综上,我们可以得到:假设我们已有 \(s_{lst},\ x,\ y\),想得到分解一次之后的状态的 \(s_{now},\ x',\ y'\)。

有:\(s_{now}=((x-1)* 2+1) * y + (s_{lst}-x* y)\)

其中 \(((x-1)* 2+1)\) 是每一个长度为 \(x\) 的数分解出来的新的三个数 二进制下 01 串拼起来的总长度。比如说 5(\((101)_2\))分解成 2,1,2,这三个数二进制下的 01 串拼起来就是 10110,长度为 5。

\((s_{lst}-x* y)\) 表示除去那些二进制表示下长度为 \(x\) 的数,剩下的、值为 0 或 1 的数的个数。因为它们不会被再次分解,所以直接保留即可。

其余的:\(x' \gets (x - 1),\ y' \gets (y * 2)\)。这个不难理解。

一直计算出新的 \(s\),直到 \(x = 1\),说明此时每个数二进制表示下的长度都是 1 了,即每个数的值非 0 即 1,分解结束。

一顿操作下来,不会浪费多少时间(因为 \(n\) 本身不超过 2 的 50 次方)。

2

建树部分。

我们现在有 \(n=10\),把它分解后的状态拆成一个树:

节点编号:

  1
 / \
2   3

每个节点代表的权值:

  0
 / \
5   5

所代表的二进制下的数:

   0
 /   \
101  101

每个节点代表的(维护的)最终的 01 串的区间:

(10 分解到最后,01 串的长度为 15。)

     (1~15)
    /       \
(1~7) (9~15)

(8 去哪里了?1 号节点本身就代表着八号节点。)

在上面这么多图中,我们发现 2 号节点和 3 号节点其实是一模一样的,所以我们只需要建 2 号节点一个就可以了。

不难发现这颗树最后的深度就是 \(n\) 在二进制表达下的 01 串的长度。

3

啊我们发现查询的最后结果就是该区间包含的数的总数量减去值为 0 的数的数量。

所以我们的树就是维护区间内值为 0 的数的数量。

查询的时候要注意如果查询范围包括了我们实际上没有建的右子树,我们要把它转化为查询左子树的对应区间。

Code

注意特判 0!!!

#include<bits/stdc++.h>
using namespace std;

#define int long long
#define rep(i, a, b) for(register int i = a; i <= b; ++i)
const int maxn = 55;
int n, tp;
int n2[maxn], tot;
int cnt;
int ans;
int x, y, s;
int ql, qr;
struct node{
	int l, r, val, num;
	int sum;
	int ch;
}t[10005];

inline int rd()
{
	int s = 0, x = 1;
	char ch = getchar();
	while(ch < '0' or ch > '9') {if(ch == '-') x = -1; ch = getchar();}
	while(ch >= '0' and ch <= '9') s = s * 10 + ch - '0', ch = getchar();
	return x * s;
}

inline int build(int l, int r)
{
	if(cnt == tot or l > r) return 0;
	int u = ++cnt;
	t[u].l = l, t[u].r = r;
	t[u].val = (n2[u] ? 0 : 1), t[u].num = l + r >> 1;
	t[u].ch = build(l, (l + r >> 1) - 1);
	t[u].sum = t[t[u].ch].sum * 2 + t[u].val;
	return u;
}

inline int query(int i, int l, int r)
{
	if(t[i].l >= l and t[i].r <= r) return t[i].sum;
	if(l > r) return 0;
	int ts = 0;
	if(t[i].num >= l and t[i].num <= r) ts = t[i].val;
	if(l < t[i].num) ts += query(t[i].ch, l, min(t[i].num - 1, r));
	if(r > t[i].num) 
	{
		int ll = max((long long)1, l - t[i].num), rr = r - t[i].num;
		ts += query(t[i].ch, ll, rr);
	}
	return ts;
}

signed main()
{
//	freopen("E:/in49.txt", "r", stdin);
	tp = n = rd();
	if(!n)
	{
		printf("0\n");
		return 0;
	}
	while(tp > 0) {n2[++tot] = tp % 2, tp /= 2;}
	s = x = tot, y = 1;
	while(x > 1) {s = ((x - 1) * 2 + 1) * y + s - x * y, x -= 1, y *= 2;}
	ql = rd(), qr = rd();
	build(1, s);
	ans = qr - ql + 1, ans -= query(1, ql, qr);
	printf("%lld\n", ans);
	return 0;
}
/*1125899906842624*/

感谢阅读。

辛苦管理员审核,如有问题烦请指出。

标签:10,Code,int,题解,ch,二进制,lst,01,CF768B
来源: https://www.cnblogs.com/gsn531/p/16496501.html