位运算和按位贪心是常用的计算和优化手段。其中,按位枚举可以将线性级别的枚举优化至 \(\log\) 级别;由于二进制的独特性质 \(2^0+2^1+\cdots+2^{k-1}<2^k\),也让从高位到低位的按位贪心成为了可能。本文接下来将介绍一系列的位运算基本技巧,并结合例题分析位运算优化的运用。
位运算的基本运算符为:&, |, ^, <<, >>, ~,分别表示按位与、或、异或、左移,右移,取反。需要注意的是,由于位运算的优先级较低,运算时最好加上括号。
假设二进制位的最低位为第 \(0\) 位,当前的数为 \(x\),则
x << 1,x >> 1;x | 1,(x|1) - 1;x | (1 << k),x (& ~(1 << k));x | ((1<<k)-1)(x >> k) & 1P4310 绝世好题: 给定一个长度为 \(n\) 的数列 \(a_i\),求 \(a_i\) 的子序列 \(b_i\) 的最长长度 \(k\),满足 \(b_i \& b_{i-1} \ne 0\),其中 \(2\leq i\leq k\),& 表示位运算取与。
这道题明显是一个有关序列分割的动态规划题目。设 \(f_i\) 表示前 \(i\) 项的最大长度且 \(i\) 必选,那么 \(f_i=\max_{j<i}\{f_j\ |\ a_j\& a_i\neq 0\} + 1\)。这样朴素的动态规划转移的时间复杂度是 \(O(n^2)\),无法通过全部的测试点。
利用位运算,我们可以不要枚举上一个位置 \(j\),而是枚举二进制下哪一位与 \(a_i\) 取与后为 \(1\)。
具体地,我们设 \(g[i,p]\) 表示前 \(i\) 项最终第 \(p\) 位为 \(1\) 的最大长度。则,我们可以枚举所有 \(a_i\) 的二进制为 \(1\) 的位置,即 \(f_i=\max\{g[i-1,p]\ |\ a_i\text{的第p位为1}\}\)。然后,对于 \(g[i,p]\),若 \(a_i\) 的第 \(p\) 位为 \(0\),那么 \(g[i,p]\leftarrow g[i-1,p]\),否则 \(g[i,p]\leftarrow \max(g[i-1,p],f_i)\)。
容易发现,\(g_i\) 数组的取值仅与 \(g_{i-1}\) 有关,因此可以通过滚动数组的方式优化空间。总的时间复杂度为 \(O(n\log \omega)\),空间复杂度为 \(O(n+\log \omega)\),其中 \(\omega\) 表示值域。
int n, a, f, g[2][31];
/*
f[i] 表示以 i 结尾的最长子序列长度
g[i, p]表示前 i项第p位为1的最大长度
f[i] = max{g[i-1, p] | a[i]第p位为1} + 1
g[i, p] =
max(g[i-1, p], f[i]), a[i]第p位为1
g[i-1, p], a[i]->p = 0
优化:g[i, p]第一维滚动
*/
int main () {
int ans = 0;
read(n);
rep(i, 1, n) {
read(a);
f = 1;
rep(p, 0, 30)
if((a >> p) & 1)
f = max(f, g[(i - 1) & 1][p] + 1);
rep(p, 0, 30)
if((a >> p) & 1)
g[i & 1][p] = max(g[(i - 1) & 1][p], f);
else g[i & 1][p] = g[(i - 1) & 1][p];
ans = max(ans, f);
}
writeln(ans);
return 0;
}
按位取与: 给定一个长度为 \(n\) 的序列 \(a_i\),请你求出 \(\max\{a_i\&a_j\}\),其中 \(1\le i<j\le n\le 10^5\),\(a_i\le 10^{18}\)。
本题暴力的复杂度为 \(O(n^2)\),显然无法通过。此时,我们需要利用按位贪心的思想。按位贪心是指从高位到低位遍历,优先使得高位为 \(1\),只有高位无法为 \(1\) 时才使这一位为 \(0\),并继续按此法则考虑下一位。其正确性证明如下:
假设目前考虑最高位为第 \(k\) 位,当第 \(k\) 位为 \(1\) 而后 \(k-1\) 位均为 \(0\) 时,收益为 \(2^k\);当第 \(k\) 位为 \(0\) 而后 \(k-1\) 位均为 \(1\) 时,收益为 \(2^{k-1}+2^{k-2}+\cdots+2^0\)。利用等比数列求和公式可得,后者等于 \(2^k-1<2^k\),因此高位为 \(1\) 更优。
对于本题,由于每一位之间的计算互相独立,可以从高到低按位贪心。假设目前访问到第 \(p\) 位,如果第 \(p\) 位为 \(1\) 的数的个数 \(\ge 2\),那么最终答案里该位为 \(1\)。此时,所有第 \(p\) 位为 \(0\) 的数都不再参与后续的计算。因此,我们可以用两个动态数组 vector 存储当前轮合法的数和下一轮合法的数,并用滚动数组的方式传递;若数组大小 \(\ge 2\) 则将 \(ans\) 的第 \(p\) 位置为 \(1\)(ans |= (1ll<<p))。
这样,时间复杂度为 \(O(n\log \omega)\),空间复杂度为 \(O(n)\)。需要注意的是,由于值域达到 long long 的范围,而 c++ 中整数默认的类型为 int,左移操作时需要使用 1ll 而非 1,这也是我在 CSP2020 时曾经犯下的错误。
ll n, a[maxn], ans, now;
vector <ll> id[2];
int main () {
read(n);
rep(i, 1, n) read(a[i]), id[0].push_back(i);
Rep(p, 30, 0) {
ll nxt = (now ^ 1);
id[nxt].clear();
for(unsigned int i = 0; i < id[now].size(); i++) {
int u = id[now][i];
if((1ll << p) & a[u])
id[nxt].push_back(u);
}
if(id[nxt].size() < 2) continue;
ans |= (1ll << p);
now = nxt;
}
writeln(ans);
return 0;
}
当然,你也可以对本题所求的内容进行拓展,比如求两两 \(\text{and, or, xor}\) 的最大值,并提交到 there。事实上,异或的最大值本质上也是按位贪心,辅以 \(01Trie\);位或的最大值则需要数位 DP。
请帮助我理解范围运算符...和..之间的区别,作为Ruby中使用的“触发器”。这是PragmaticProgrammersguidetoRuby中的一个示例:a=(11..20).collect{|i|(i%4==0)..(i%3==0)?i:nil}返回:[nil,12,nil,nil,nil,16,17,18,nil,20]还有:a=(11..20).collect{|i|(i%4==0)...(i%3==0)?i:nil}返回:[nil,12,13,14,15,16,17,18,nil,20] 最佳答案 触发器(又名f/f)是
我明白了:x,(y,z)=1,*[2,3]x#=>1y#=>2z#=>nil我想知道为什么z的值为nil。 最佳答案 x,(y,z)=1,*[2,3]右侧的splat*是内联扩展的,所以它等同于:x,(y,z)=1,2,3左边带括号的列表被视为嵌套赋值,所以它等价于:x=1y,z=23被丢弃,而z被分配给nil。 关于ruby-带括号和splat运算符的并行赋值,我们在StackOverflow上找到一个类似的问题: https://stackoverflow
问题是:除了在“OperatorExpressions”?例如:1%!2 最佳答案 是的,可以创建自定义运算符,但有一些注意事项。Ruby本身并不直接支持它,但是superatorsgem做了一个巧妙的把戏,将运算符链接在一起。这允许您创建自己的运算符,但有一些限制:$geminstallsuperators19然后:require'superators19'classArraysuperator"%~"do|operand|"#{self}percent-tilde#{operand}"endendputs[1]%~[2]#Out
在Ruby中有运算符(operator)。在API中,他们没有命名它的名字,只是:Theclassmustdefinetheoperator...Comparableusestoimplementtheconventionalcomparison......theobjectsinthecollectionmustalsoimplementameaningfuloperator...它叫什么名字? 最佳答案 参见上面的@Tony。然而,它也被称为(俚语)“宇宙飞船运算符(operator)”。
也许这听起来很荒谬,但我想知道这对Ruby是否可行?基本上我有一个功能...defadda,bc=a+breturncend我希望能够将“+”或其他运算符(例如“-”)传递给函数,这样它就类似于...defsuma,b,operatorc=aoperatorbreturncend这可能吗? 最佳答案 两种可能性:以方法/算子名作为符号:defsuma,b,operatora.send(operator,b)endsum42,23,:+或者更通用的解决方案:采取一个block:defsuma,byielda,bendsum42,23,
可能真的很简单,但我很难在网上找到关于这个的文档我在Ruby中有两个activerecord查询,我想通过OR运算符连接在一起@pro=Project.where(:manager_user_id=>current_user.id)@proa=Project.where(:account_manager=>current_user.id)我是ruby的新手,但我自己尝试使用||@pro=Project.where(:manager_user_id=>current_user.id||:account_manager=>current_user.id)这没有用,所以1.我想知道如何在
我是Ruby和这个网站的新手。下面两个函数是不同的,一个在函数外修改变量,一个不修改。defm1(x)x我想确保我理解正确-当调用m1时,对str的引用被复制并传递给将其视为x的函数。运算符当调用m2时,对str的引用被复制并传递给将其视为x的函数。运算符+创建一个新字符串,赋值x=x+"4"只是将x重定向到新字符串,而原始str变量保持不变。对吧?谢谢 最佳答案 String#+::str+other_str→new_strConcatenation—ReturnsanewStringcontainingother_strconc
ruby中有这样的东西吗?send(+,1,2)我想让这段代码看起来不那么冗余ifop=="+"returnarg1+arg2elsifop=="-"returnarg1-arg2elsifop=="*"returnarg1*arg2elsifop=="/"returnarg1/arg2 最佳答案 是的,只需像这样使用send(或者更好的是public_send):arg1.public_send(op,arg2)这是可行的,因为Ruby中的大多数运算符(包括+、-、*、/、andmore)只需调用方法。所以1+2与1.+(2)相同
您如何找到有关代码中运算符用法的信息(最好是通过Google)?在这种情况下,我想找到这段代码在Ruby中的含义。x=[1,2,3]x.send:[]=,0,2x[0]+x.[](1)+x.send(:[],2)我要你教我如何钓鱼——不要告诉我运算符(operator)是做什么的。当我去Google并尝试搜索符号时,我得到的示例或教程没有涵盖特定的用法。https://stackoverflow.com/questions/1165786/how-to-search-for-punctuation-that-gets-ignored-by-google表示谷歌驳回了这种表示法;我寻找“
重新定义Float#/似乎没有效果:classFloatdef/(other)"magic!"endendputs10.0/2.0#=>5.0但是当另一个中缀运算符Float#*被重新定义时,Float#/突然采用了新的定义:classFloatdef/(other)"magic!"enddef*(other)"spooky"endendputs10.0/2.0#=>"magic!"我很想知道是否有人可以解释这种行为的来源,以及其他人是否得到相同的结果。ruby:ruby2.0.0p353(2013-11-22)[x64-mingw32]要快速确认错误,请运行thisscript.