C++ 算法篇 位运算
?學習目標
?1. 理解與掌握 C++ 中的位運算。
?2. 靈活應用位運算優化程序。
任何信息在計算機中都是采用二進制表示的,數據在計算機中是以補碼形式存儲的,位運算就是直接對整數在內存中的二進制位進行運算。由于位運算直接對內存數據進行操作,不需要轉換成十進制,因此處理速度非常快,在信息學競賽中往往可以優化理論時間復雜度的系數。同時,一個整數的各個二進制位互不影響,利用位運算的一些技巧可以幫助我們簡化程序代碼。
一、位運算符
C++ 提供了按位與(&)、按位或(| )、按位異或(^)、取反(~)、左移(<<)、右移(>>)這 6 種位運算符。 ?這些運算符只能用于整型操作數,即只能用于帶符號或無符號的 char、short、int 與 long 類型。
?
(1)按位與運算符(&)?
“a&b”是指將參加運算的兩個整數a和b,按二進制位進行“與”運算。運算規則:0&0=0; ?0&1=0; ? 1&0=0; ? ?1&1=1;? ? ? 即:兩位同時為“1”,結果才為“1”,否則為0
例如:3&5 ?即 0000 0011& 0000 0101 = 0000 0001 ?因此,3&5的值得1。
另,負數按補碼形式參加按位與運算。
按位與&比較實用的例子:
1、比如我們經常要用的是否被2整除,一般都寫成 ? if(n % 2 == 0) 可以換成 if((n&1) == 0)?
2、按位與運算可以取出一個數中指定位。例如:要取出整數84從左邊算起的第3、4、5、7、8位,只要執行84 & 59,因為84對應的二進制為01010100,59對應的二進制為00111011,01010100 & ?00111011= ?00010000 ? 可知84從左邊算起的第3、4、5、7、8位分別是0、1、0、0、0。
?3、清零。如果想將一個單元清零,使其全部二進制位為0,只要與一個各位都為零的數值相與,結果為零。
按位與應用舉例1:
整數冪:判斷一個數n ,是不是2的整數冪。比如:64=2^6,所以輸出“yes”,而65無法表示成2的整數冪的形式,所以輸出“NO”。
#include<bits/stdc++.h> using namespace std; int main() { int n;cin>>n;if(n&(n-1))cout<<"NO";else cout<<"Yes"; }按位與應用舉例2:
計算一個數的二進制中1的個數:
算法1:通過與初始值為1的標志位進行與運算,判斷最低位是否為1;然后將標志位左移,判斷次低位是否為1;一直這樣計算,直到將每一位都判斷完畢。
#include<bits/stdc++.h> using namespace std; int main() { int n = 0,num;unsigned int flag = 1;cin>>num;while(flag){ if(num & flag) n++;flag = flag << 1;}cout<<n; }算法2:還有一種方法,一個整數減一,可以得到該整數的最右邊的1變為0,這個1右邊的0變為1。對這個整數和整數減一進行與運算,將該整數的最右邊的1變為0,其余位保持不變。直到該整數變為0,進行的與運算的次數即為整數中1的個數。
#include<bits/stdc++.h> using namespace std; int main() { int n = 0,num;unsigned int flag = 1;cin>>num;while(num){ num = num & (num - 1);n++;}cout<<n; }?
(2)按位或運算符(|)?
參加運算的兩個對象,按二進制位進行“或”運算。運算規則:0|0=0; ?0|1=1; ?1|0=1; ? 1|1=1;
? ? ?即 :參加運算的兩個對象只要有一個為1,其值為1。
例如:3|5 即 00000011 | 0000 0101 = 00000111 ?因此,3|5的值得7。
另,負數按補碼形式參加按位或運算。
?按位或 (|) 比較實用的例子
可以用一個unsigned int 來存儲多個布爾值。比如一個文件有讀權限,寫權限,執行權限。看起來要記錄3個布爾值。我們可以用一個unsigned int也可以完成任務。
一個數r來表示讀權限,它只更改個位來記錄讀權限的布爾值? ? ? ? ?00000001 ?(表示有讀權限)? ? ?00000000 ?(表示沒有讀權限)
一個數w表示寫權限,它只用二進制的倒數第二位來記錄布爾值? ? 00000010 (表示有寫權限)? ? ?00000000 (表示沒有寫權限)
一個數x表示執行權限,它只用倒數第三位來記錄布爾值? ? ? ? ? ? ? 00000100 (表示有執行權限)? ? 00000000 (表示沒有執行權限)
那么一個文件同時沒有3種權限就是? ? ? ? ? ?~r | ~ w | ~ x 即為 00000000,就是0
只有讀的權限就是? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?r | ~w | ~x 即為 00000001,就是1
只有寫的權限就是? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ~r | w | ~x 即為 00000010,就是2
一個文件同時有3種權限就是? ? ? ? ? ? ? ? ? ? ? ?r | w | x 即為 00000111,就是7
?
(3)按位異或運算符(^)
參加運算的兩個數據,按二進制位進行“異或”運算。
運算規則:0 ^ 0=0; ?0 ^ 1=1; ?1^ 0=1; ? 1^1=0;
? ?即:參加運算的兩個對象,如果兩個相應位為“異”(值不同),則該位結果為1,否則為0。
下面重點說一下按位異或,異或 ?其實就是不進位加法,如1+1=0,,0+0=0,1+0=1。
異或的幾條性質:
1、交換律:a ^ b=b ^ a
2、結合律:(a ^ b) ^ c == a^ (b ^ c)
“異或運算”的特殊作用:
(1)使特定位翻轉: ? 例:X=10101110,使X低4位翻轉,用X ^ 0000 1111 = 1010 0001即可得到。
(2)與0相異或,保留原值 ,10101110^ 00000000 = 1010 1110。
(3)對于任何數x都有――自反性:x^ x=0,x^ 0=x ? ?例如:A^B ^ B = A
(4)交換二個數:a ?=a ^ b; ? b = b ^ a; ?a = a ^ b;
按位異或應用舉例1:
給出 n 個整數,n 為奇數,其中有且僅有一個數出現了奇數次,其余的數都出現了偶數次。用線性時間復雜度、常數空間復雜度找出出現了奇數次的那個數。
【輸入樣例】
9
3 3 7 2 4 2 5 5 4
【輸出樣例】
7
按位異或 應用舉例2:
1-1000放在含有1001個元素的數組中,只有唯一的一個元素值重復,其它均只出現
一次。每個數組元素只能訪問一次,設計一個算法,將它找出來;不用輔助存儲空
間,能否設計一個算法實現?
解法一、顯然已經有人提出了一個比較精彩的解法,將所有數加起來,減去1+2+...+1000的和。
這個算法已經足夠完美了,相信出題者的標準答案也就是這個算法,唯一的問題是,如果數列過大,則可能會導致溢出。
解法二、異或就沒有這個問題,并且性能更好。
將所有的數全部異或,得到的結果與1^2^3^...^1000的結果進行異或,得到的結果就是重復數。
按位異或 應用舉例3:
一系列數中,除兩個數外其他數字都出現過兩次,求這兩個數字,并且按照從小到大的順序輸出.例如 2 2 1 1 3 4.最后輸出的就是3 和4
#include<bits/stdc++.h> using namespace std; int a[1000]; int main() { ? ? ? int n;scanf("%d", &n);int x = 0;for(int i = 1; i <= n; i++) {? ? ? scanf("%d", &a[i]); x ^= a[i];? ? }int num1 = 0, num2 = 0;int tmp = 1;while(!(tmp & x)) tmp <<= 1;cout<<tmp<<endl;for(int i = 1; i <= n; i++) {if(tmp & a[i]) num1 ^= a[i];else num2 ^= a[i];}printf("%d %d\n", min(num1, num2), max(num1, num2));return 0; }?
(4)按位取反運算符(~)
按位取反運算符(~)是指將整數的各個二進制位都取反,即1變為0,0變為1。
例如,~9=-10,因為9(00001001)所有位取反即為(11110110),這個數最高位是1,所以是補碼。補碼還原成反碼(反碼等于補碼減1)得到(11110101),再還原為原碼(反碼到原碼最高位不變,其它各位取反)等于(10001010),? ? ?十進制為-10。
?
(5)按位左移運算符(<<)
左移運算符是用來將一個數的各二進制位左移若干位,移動的位數由右操作數指定(右操作數必須是非負值),其右邊空出的位用0填補,高位左移溢出則舍棄該高位。
在高位沒有1的情況下,左移1位相當于該數乘以2,左移2位相當于該數乘以2*2=4,15<<2=60,即乘了4。
但此結論只適用于該數左移時被溢出舍棄的高位中不包含1的情況。
例如:143<<2 ?結果為60 ? 因為143轉換為進制為10001111,左移2得00111100 ,結果為60。
?
(6)按位右移運算符(>>)
右移運算符是用來將一個數的各二進制位右移若干位,移動的位數由右操作數指定(右操作數必須是非負值),移到右端的低位被舍棄,對于無符號數,高位補0。對于有符號數,某些機器將對左邊空出的部分用符號位填補(即“算術移位”),而另一些機器則對左邊空出的部分用0填補(即“邏輯移位”)。
注意:對無符號數,右移時左邊高位移入0;對于有符號的值,如果原來符號位為0(該數為正),則左邊也是移入0。
如果符號位原來為1(即負數),則左邊移入0還是1,要取決于所用的計算機系統。有的系統移入0,有的
系統移入1。移入0的稱為“邏輯移位”,即簡單移位;移入1的稱為“算術移位”。?
例: a的值是八進制數113755:?
? ?a:1001011111101101 (用二進制形式表示)
? ?a>>1: 0100101111110110 (邏輯右移時)
? ?a>>1: 1100101111110110 (算術右移時)
? ?在有些系統中,a>>1得八進制數045766,而在另一些系統上可能得到的是145766。Turbo C和其他一些C
編譯采用的是算術右移,即對有符號數右移時,如果符號位原來為1,左面移入高位的是1。
源代碼:
#include <stdio.h>
main()
{?int a=0113755;?printf("%d",a>>1);}
(7)位運算優先級
總的來說比較低,邏輯運算符和數學運算符出現在同一個表達式中,那么需要用括號來表達運算次序。
(8)復合賦值運算符
位運算符與賦值運算符結合,組成新的復合賦值運算符,它們是:
1、&= ? 例:a &=b ? ? ? 相當于a=a& b
2、|= ? 例:a |=b? ? ? ? ? ?相當于a=a |b
3、>>= ?例:a >>=b ? ?相當于a=a>> b
4、<<= 例:a<<=b ? ? ?相當于a=a<< b
5、^= ? 例:a ^= b? ? ? ?相當 ?a=a ^b
運算規則:和前面講的復合賦值運算符的運算規則相似。
(9)不同長度的數據進行位運算
如果兩個不同長度的數據進行位運算時,系統會將二者按右端對齊,然后進行位運算。
以“與”運算為例說明如下:如果一個4個字節的數據與一個2個字節數據進行“與”運算,右端對齊后,左邊不足的位依下面三種情況補足:
(1)如果整型數據為正數,左邊補16個0。
(2)如果整型數據為負數,左邊補16個1。
(3)如果整形數據為無符號數,左邊也補16個0。
?
(10)下面列舉一些常見的二進制位的變換操作
| 去掉最后一位? | 101101->10110 | x>>1 |
| 在最后加一個0 | 101101->1011010 | x<<1 |
| 在最后加一個1? | 101101->1011011 | (x<<1)+1 |
| 把最后一位變成1 | 101100->101101? | ?x | 1 |
| 把最后一位變成0? | 101101->101100 | (x |1) - 1 |
| 最后一位取反 | 101101->101100 | x ^ 1 |
| 把右數第K位變成1 | 101001->101101,k=3 | x ?| (1<<(k-1)) |
| 把右數第K位變成0 | 101101->101101,k=3 | x & ~(1<<(k-1)) |
| 右數第k位取反? | 101001->101101,k=3? | ?x ^ (1<<(k-1)) |
| 取末三位 | 1101101->101 | ?x &7 |
| 取末k位 | 1101101->1101,k=5? | ?x & (1<<k-1) |
| 取右數第k位 | 1101101->1,k=4 | x >> (k-1)&1 |
| 把末k位變成1? | 101001->101111,k=4 | x|(1<<k-1) |
| 末k位取反? | 101001->100110,k=4? | ?x^(1<<k-1) |
| 把右邊連續的1變成0? | ?100101111->100100000 | ?x&(x+1) |
| 把右起第一個0變成1 | 100101111->100111111 | ?x|(x+1) |
| 把右邊連續的0變成1 | 11011000->11011111 | x|(x-1) |
| 取右邊連續的1 | 100101111->1111 | (x^(x+1))>>1 |
| 去掉右起第一個1的左邊 | ?100101000->1000? | x&(x^(x-1)) |
??
最后一個會在樹狀數組中用到
?
綜合練習:
1、拔河比賽:
題目描述:
一個的學校要舉行拔河比賽,為了在賽前鍛煉大家,老師決定把班里所有人分為兩撥,進行拔河因為為鍛煉所以為了避免其中一方的實力過強老師決定以體重來劃分隊伍,盡量保持兩個隊伍的體重差最少。
輸入格式:? ? 第一行為人數(1<=n<=100),從第二行開始是每個人的體重m,
輸出格式:? ? 最小體重差。
輸入樣例:
3
100? 90? 200
輸出樣例:
10
數據范圍:
60%的數據保證:(0<=n<=100)? (0<=m<=500)
100%的數據保證:(0<=n<=500)? (0<=m<=1000)
?
2、起床困難綜合癥:(洛谷)
題目描述:
21世紀,許多人得了一種奇怪的病:起床困難綜合癥,其臨床表現為:起床難,起床后精神不佳。作為一名青春陽光好少年,atm一直堅持與起床困難綜合癥作斗爭。通過研究相關文獻,他找到了該病的發病原因: 在深邃的太平洋海底中,出現了一條名為drd的巨龍,它掌握著睡眠之精髓,能隨意延長大家的睡眠時間。 正是由于drd的活動,起床困難綜合癥愈演愈烈, 以驚人的速度在世界上傳播。為了徹底消滅這種病,atm決定前往海底,消滅這條惡龍。歷經千辛萬苦,atm終于來到了drd所在的地方,準備與其展開艱苦卓絕的戰斗。drd有著十分特殊的技能,他的防御戰線能夠使用一定的運算來改變他受到的傷害。具體說來,drd的防御戰線由n扇防御門組成。每扇防御門包括一個運算op和一個參數t,其中運算一定是OR,XOR,AND中的一種,參數則一定為非負整數。如果還未通過防御門時攻擊力為x,則其通過這扇防御門后攻擊力將變為x op t。最終drd受到的傷害為對方初始攻擊力x依次經過所有n扇防御門后轉變得到的攻擊力。
由于atm水平有限,他的初始攻擊力只能為0到m之間的一個整數(即他的初始攻擊力只能在 0, 1, … , m中任選,但在通過防御門之后的攻擊力不受m的限制)。為了節省體力,他希望通過選擇合適的初始攻擊力使得他的攻擊能讓drd受到最大的傷害,請你幫他計算一下,他的一次攻擊最多能使drd受到多少傷害。
輸入格式:
輸入文件的第 1 行包含 2 個整數,依次為n, m,表示 drd 有n扇防御門,atm 的初始攻擊力為0到m之間的整數。
接下來n行,依次表示每一扇防御門。每行包括一個字符串op和一個非負整數t,兩者由一個空格隔開,且op在前,t在后,op表示該防御門所對應的操作,t表示對應的參數。
輸出格式:
輸出一行一個整數,表示atm的一次攻擊最多使drd受到多少傷害。
輸入樣例:
3 10 AND 5 OR 6 XOR 7輸出樣例:
1
數據范圍:
100%數據保證(0<=m,t<=10^9)
?
3、乒乓游戲:
一條大街上住著n個乒乓球愛好者,經常組織比賽切磋技術,每個人都有一個不同的技能值a(i)。每場比賽需要3個人:兩名選手,一名裁判。他們有一個奇怪的規定,即裁判必須住在兩名選手的中間,并且技能值也在兩名選手之間。問一共能組織多少種比賽。
輸入格式:
第一行? ? 為數據組數T(1<=T<=20)每組數據占一行,首先是整數n(3<=n<=20000),
第二行? ? 然后是n個不同的整數,即a(1),a(2)……a(n)(1<=a(i)<=100000),按照住所從左到右的順序給出每個乒乓愛好者的技能值。
輸出格式:
對于每組數據,輸出比賽總數的值。
輸入樣例:
5
6 1 8 1 0 1
輸出樣例:
3
數據范圍:
30%的數據保證:n<=3000
100%的數據保證:n<=3000
4、高低位交換
題目描述
給出一個小于2^32的正整數。這個數可以用一個32位的二進制數表示(不足32位用0補足)。我們稱這個二進制數的前16位為“高位”,后16位為“低位”。將它的高低位交換,我們可以得到一個新的數。試問這個新的數是多少(用十進制表示)。
例如,數1314520用二進制表示為0000 0000 0001 0100 0000 1110 1101 1000(添加了11個前導0補足為32位),其中前16位為高位,即0000 0000 0001 0100;后16位為低位,即0000 1110 1101 1000。將它的高低位進行交換,我們得到了一個新的二進制數0000 1110 1101 1000 0000 0000 0001 0100。它即是十進制的249036820。
輸入格式
一個小于2^32的正整數
輸出格式
將新的數輸出
輸入輸出樣例
輸入
1314520輸出?
249036820?
?
總結
以上是生活随笔為你收集整理的C++ 算法篇 位运算的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: nth-of-type和nth-chil
- 下一篇: s3c2440移植MQTT