首页 > 其他分享 >LuoguP7637 [BalticOI 2006 Day 1] BITWISE EXPRESSIONS

LuoguP7637 [BalticOI 2006 Day 1] BITWISE EXPRESSIONS

时间:2023-08-25 15:55:17浏览次数:48  
标签:二进制 int BITWISE 二进制位 num 2006 BalticOI bit 数据

题目大意

给定 \(N\) 对数据,每对数据包含两个整数 \(A_i\) 和 \(B_i\),表示这一对数据的 \(v_i\) 的范围:\(A_i \leq v_i \leq B_i\)。又将这 \(N\) 对数据分为 \(P\) 组,其中 \(K_i\) 表示第 \(i\) 组数据中有多少对数据。

我们设第 \(i\) 组数据中将所有数按位与的结果为 \(X_i\),求 \(X_1\&X_2\&...X_P\) 的最大值。

题目分析

思路:贪心

很容易看出来,我们要想最终的结果最大,那么我们需要尽可能地满足最高位的需求,也就是优先考虑将最高位置为 \(1\)。

这个贪心很容易就可以证明出来,因为对于一个二进制数,如果这个二进制数的第 \(x\) 为由 \(0\) 变成了 \(1\),那么它将会增加 \(2^{x-1}\)。相较于两种方案,将当前最高位 \(x\) 从 \(0\) 变成 \(1\),或是将所有低于 \(x\) 位的都变成 \(1\),显然,\(2^{x-1}>\sum_{i=x-2}^02^i\)。说明这个贪心是正确的。

我们为了方便操作,用 \(st\) 数组表示每一组数据的开头的下标。输入:

n = read(),p = read();
st[1] = 1;
for (int i = 1;i <= p;i++){
	int x = read();
	st[i + 1] = st[i] + x;
}
for (int i = 1;i <= n;i++){
	arr[i].l = read(),arr[i].r = read();
}

解决问题时,我们会遇到几个问题。

\(\bullet\) 我们如何知道一个连续的区间内存不存在一个数的二进制上的某一位上为 \(1\)?

对于这个问题,我们可以用一些巧妙的位运算将这个过程变成 \(O(1)\) 的复杂度。

对于一个连续的区间 \([l,r]\),我们要判断这连续的区间内存不存在一个数的二进制的第 \(x\) 位上为 \(1\)。首先判断 \(l\) 的第 \(x\) 位上是不是 \(1\),如果是,就说明存在。否则,我们找到第一个大于 \(l\) 的二进制数的第 \(x\) 位上为 \(1\) 的数,用这个数判断与 \(r\) 之间的关系,若小于等于 \(r\),则存在,否则不存在。

根据刚才贪心的证明,我们很容易得到第一个大于 \(l\) 的符合要求的二进制数,即把 \(l\) 的 \(x\) 位赋值为 \(1\),把 \(x\) 位后的所有位置变成 \(0\)。用代码表示为:

(l + (1ll << (bit - 1))) & -(1ll << (bit - 1)

\(\bullet\) 每对数据是一个区间,如果两次取到不同的数,并且两个数的二进制完全不一样,导致了错误怎么办?

我们可以将每对数据已经确定了的位数存入一个数组 \(done\) 里,\(done_{num,i}\) 表示第 \(num\) 对数据中,第 \(i\) 个已经确定出的位置(即已经贡献出了的二进制位),这样我们每次选数的时候,判断一下当前这个数具不具备这一对数据中的条件(即包不包括之前确定了的二进制位)。

这意味着我们可以将不包括之前确定了的二进制位的数直接舍弃吗?答案肯定是不可以。

因为这仅仅代表这对数据中的其中一个数,直接舍弃就太片面了,我们考虑若他不具备之前已经确定的二进制位,我们就强行把这些二进制位加上,看加上之后会不会超出原本数据的范围。即:

int add_bit(int x,int num){ // x 表示第一个大于 l 的符合要求的二进制数,num 表示第 num 对数据。
	for (int i = 1;i <= donei[num];i++){ // donei 用于表示下标。
		if (((x >> (done[num][i] - 1)) & 1) == 0){ // 如果不包括之前已经确定的二进制位。
			x += (1ll << (done[num][i] - 1)); // 强行加上。
		}
	}
	return x;
}

这样我们如果 \(x>r\) 我们还不能着急去舍弃他。因为 \(x\) 的二进制中还有可能存在其他不必要的二进制数,也就是除了必要的已经确定的二进制位以外,还存在 \(x\) 自身本来就有的二进制位,我们尝试把这些二进制位适当的取舍,看看最终能不能满足条件。

这里有个细节,我们枚举位数时应该从大到小,不然如果从小到大的话,越到后面可能多减的也就越多。

bool in(int bit,int num){ // 用来判断第 bit 位是不是第 num 对数据必要的二进制位。
	for (int i = 1;i <= donei[num];i++){ // 枚举。
		if (bit == done[num][i]){ // 是必要的二进制位。
			return 1;
		}
	}
	return 0;
}
bool delcheck(int x,int l,int r,int num,int bit){
	for (int i = 32;i >= 1;i--){ // 根据题目给出的数据范围,二进制下最多 32 位。
		if (!in(i,num) && ((x >> (i - 1)) & 1) && i != bit){ // 如果第 i 为不是必要的二进制位,并且目前第 i 位恰好为 1,并且第 i 位也不是现在要加的二进制位。
			if (x - (1ll << (i - 1)) >= l && x - (1ll << (i - 1)) <= r){ // 如果减去这一位后满足范围,返回真。
				return 1;
			}
			else if (x - (1ll << (i - 1)) > r){ // 如果减去这一位后还是太大了,那么先减去,看后面有没有机会继续减。
				x -= (1ll << (i - 1));
			}
            // 如果减去太小了,就不减这一位,即不作任何操作。
		}
	}
	return 0; // 如果所有位数都枚举完了还不满足条件,返回假。
}

那么此时此刻我们的 \(\operatorname{check}\) 函数就可以写出来了(用来判断当前这对数据能否贡献)。

bool isgreater(int x,int l,int r,int num,int bit){ // 判断是否大于 r
	if (x > r){ // 如果大于 r
		return delcheck(x,l,r,num,bit); // 进行上文所说的删位处理。
	}
	return 1; // 不大于r,返回真。
}
bool check(int l,int r,int bit,int num){
	if ((l >> (bit - 1)) & 1){ // 如果 l 的第 bit 位上本来就是 1。
		int x = add_bit(l,num); // 判断 l 的二进制包不包括之前已经确定的二进制位,没有则加上。
		return isgreater(x,l,r,num,bit); // 判断是否大于 r。
	}
	int x = ((l + (1ll << (bit - 1))) & -(1ll << (bit - 1))); // 上文所描述的第一个大于 l 的满足条件的数。
	x = add_bit(x,num); // 判断 x 的二进制包不包括之前已经确定的二进制位,没有则加上。
	return isgreater(x,l,r,num,bit); // 判断是否大于 r。
}

\(\bullet\) 如果一个组内刚开始全都是一对数据在贡献,其他数据都不做贡献,会对最优解产生影响吗?

当然会,比如前面几位都是一对数据在贡献,这个时候出来一位,这一对数据本来是可以贡献的,但是由于前面几位数的贡献,这对数据容不下这一位了,并且组内的其他数据也无法贡献出这一位(其他数据不是因为容不下,而是区间内根本没有这一位)。但如果其他数据可以帮忙分担一下前面几位,那么就可以给其他数据留下充足的空间来存储别的数据根本没有的位数。

这个时候我们开一个数组 \(times\),\(time_i\) 表示第 i 对数据已经贡献出的次数。我们每次在组内找出能够存储这一位的,并且贡献次数最小的来存储当前这一位即可,这样我们就把数据的利用价值最大化了。

int ans = 0; // 统计答案
for (int bit = 32;bit >= 1;bit--){ // 枚举位数。
	int cnt = 0; // 统计能够贡献出当前位的组数。
	for (int idx = 1;idx <= p;idx++){ // 枚举组。
		int minn = 2147483647,mini; // 查找贡献最小的。
		for (int i = st[idx];i <= st[idx + 1] - 1;i++){ // 枚举组内每对数据。
			if (check(arr[i].l,arr[i].r,bit,i)){ // 判断当前数据能否贡献。
				if (minn >= times[i]){ // 找已经贡献的最小的数据。
					minn = times[i];
					mini = i;
				}
			}
		}
		if (minn != 2147483647){ // 如果有能贡献的。
			cnt++; // 统计组数。
			usei[cnt] = mini; // 统计这一组的贡献者。
			times[mini]++; // 将这一组的贡献者的贡献次数 +1。
		}
	}
	if (cnt == p){ // 如果贡献的组数等于总组数,说明这一位可以变成 1。
		for (int i = 1;i <= p;i++){ // 将当前已经确认的位数,统计到 done 数组里面。
			donei[usei[i]]++;
			done[usei[i]][donei[usei[i]]] = bit;
		}
		ans += (1 << (bit - 1)); // 统计答案。
	}
}
printf("%lld",ans);

那么这样一来这道题就已经完成了。总的代码就不放了,把上文的几段合起来就行了。

总结

这道题有一点思维难度,还要求了对于位运算的掌握。特别是对细节的处理很重要,经常忘记想到一些其他的情况而导致出错。

标签:二进制,int,BITWISE,二进制位,num,2006,BalticOI,bit,数据
From: https://www.cnblogs.com/WiuehPlus/p/17657151.html

相关文章

  • [刷题笔记] Luogu P1064 [NOIP2006 提高组] 金明的预算方案
    ProblemAnalysis我们发现如果忽略主从关系,那这道题就是一个裸的01背包问题。主从关系处理也非常简单,借鉴P2014选课的经验,转换成树上背包问题。同理,本题是一个森林,若将0号节点参与建树的话就可以把森林转换成树,处理方便。具体地,设\(f_{i,j}\)表示以\(i\)为父节点,剩......
  • P4795 [BalticOI 2018] 基因工程 题解
    题目传送门:Click。蒟蒻看见这道题,想了足足一个小时,过后顿有所悟,故作此篇。首先,看到题目,光是数据就已经达到了\(\operatorname{O}(nm)\)的级别,再看一看数据范围:\(3\leqn,m\leq4,100\)。显然是一道时间复杂度为\(\operatorname{O}(n,m)\)级别的题目。本蒟蒻首先观察了样......
  • P1060 [NOIP2006 普及组] 开心的金明 题解
    思路01背包模版题,唯一不同的是加了一个条件就是价格与重要度的乘积。转移方程为:dp[j]=max(dp[j],dp[j-w[i]]+w[i]*v[i]);这里加了滚动数组优化。代码#include<bits/stdc++.h>#definelllonglong#defineldlongdoubleusingnamespacestd;inlinevoidread(int&x){......
  • CF1004F Sonya and Bitwise OR
    考虑只有一次询问的时候怎么做。显然的cdq分治,每次分治区间\([l,r]\),统计跨过\(p=\lfloor\frac{l+r}{2}\rfloor\)的区间的个数。可以枚举区间左端点,由于右端点右移时区间或单调非降,可以双指针维护。充分发掘题目条件,由于是区间或,还有一个很套路的性质:一个位置\(x\),以其为......
  • 洛谷 P2458 [SDOI2006] 保安站岗 - 树形DP
    P2458保安站岗思路:树形DP三个状态:dp[i][0]:节点i位置放保安的最小花费dp[i][1]:节点i位置不放保安,但被子节点的保安看守dp[i][2]:节点i位置不放保安,但被父节点的保安看守状态转移:dp[i][0]:节点i位置放保安,那么它可以合并子节点任何状态的最小值;dp[i][1]:节点i位......
  • P6227 [BalticOI 2019 Day1] 山谷
    P6227[BalticOI2019Day1]山谷Description给一棵树,一个根,一些特殊补给点,一些询问。求解如下问题:断掉一条边\(u\tov\),这样以后你能否从给定的\(R_i\)走到根,若能输出escaped。不能到达根且不能到达任何一个特殊补给点输出oo。若不能到达根但可以到达特殊补给点输出边权和......
  • 题解 P7640 [BalticOI 2006 Day 2] CITY PLANNING
    首先我们定义“圈”为与原点距离相等的点集。...3.....323...32123.3210123.32123...323.....3...暴力:把圈放到堆里,然后每次取出代价最小的一圈,修改当前圈的楼层,向外圈拓展。正解:考虑二分。如果是二分最终答案,我们会发现......
  • 题解 P7250 [BalticOI 2012 Day1] 山峰
    通过观察,可以发现此题和最小生成树十分相似(两个地点之间途经的最小值最大)。于是可以考虑这么做:通过bfs将每一个块预处理出来,并记录其编号、高度、类型(是否为高地)以及边缘的点。将每一个块按高度从大到小排序。依次枚举每个块:对于当前要处理的块,枚举其边界的所有点,......
  • 「BalticOI 2011 Day2」Tree Mirroring 题解
    本文网址:https://www.cnblogs.com/zsc985246/p/17539182.html,转载请注明出处。题目大意现在有一棵树\(T\),复制一个完全相同的\(T'\),并将这两棵树的叶子节点全部对应合并在一起,形成一个图,我们称这种图为对称图。给定一个图,判断它是否为对称图。\(3\len,m\le10^5\)思路......
  • P4645 [COCI2006-2007#3] BICIKLI
    P4645[COCI2006-2007#3]BICIKLI题意:求一张\(n\)个点的有向图中\(1\)号点到\(2\)号点的路径数。首先考虑不在\(1\)号点到\(2\)号点的路径上的那些点不会对答案产生影响,于是先预处理出所有\(1\)号点到\(2\)号点路径上经过的点。先在原图上以\(1\)号点为起点对所......