基礎演算法題——異或和之和(位運算、組合數)

小白小鄭發表於2020-10-13

異或和之和

題目連結
題目1
題目2


解題思路

解題方案
暴力列舉:時間複雜度:O(n3),超時。

位操作+組合數:解鈴還須繫鈴人。對於這種與、或、異或的位操作,一般也是通過位操作來解答。


總結規律
題目要求在 n 個正整數中列舉 3 個數進行位操作,若要確定 3 個數的異或結果,就要尋找對異或結果的位有影響的情況。
列舉可能情況:
0 ^ 0 ^ 0 = 0
0 ^ 0 ^ 1 = 1
0 ^ 1 ^ 0 = 1
0 ^ 1 ^ 1 = 0
1 ^ 0 ^ 0 = 1
1 ^ 0 ^ 1 = 0
1 ^ 1 ^ 0 = 0
1 ^ 1 ^ 1 = 1
①、當 3 個都為 1 時,異或的結果為 1 。
②、當 2 個為 0 時,異或結果為 1 。
③、除了 ①、② 外,其餘情況為 0 。


程式碼解析
①、列舉每一位二進位制數的值,先對 mod 求餘防止溢位。
eg:1(1), 2(10), 4(100), 8(1000)…

f[0]=1;
for(int i=1; i<64; i++)
	f[i] = (f[i-1]<<1)%mod;

②、統計 n 個整數的每一位 1 的個數及 0 的個數。
0 的個數 = n - 1 的個數。可以不必再求。

//統計 n 個整數每一位為 1 的數量
//eg:num[j]: n 個整數第 j 位為 1 的數量
for(int i=0; i<n; i++){
	ll tmp;
	scanf("%lld", &tmp);
	for(int j=0; j<64; j++)
		num_1[j] += (tmp>>j)&1;
}

③、計算出每一位異或和結果能夠得到多少個 1 ( k ) ,最後將答案與該位大小 * 1 的個數( f [ i ] * k ) 累加起來,並求餘。

for(int i=0; i<64; i++){
	ll one=num_1[i], zero=n-num_1[i], k; 
	//1 0 0 | 0 1 0 | 0 0 1
	if(zero>=2 && one>=1){ 
     	k = zero*(zero-1)*one/2%mod;
    	ans = (ans+k*f[i]%mod)%mod;
    }
    //1 1 1
    if(one>=3){
       	k = one*(one-1)*(one-2)/6%mod;
	   	ans = (ans+k*f[i]%mod)%mod;
    }
}

注意優先順序 :
‘*’、’/’ > ‘+’、’-’ > ‘%’ > ‘<<’、’>>’ > ‘=’


實現程式碼

#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll ans=0;
int f[64]={0}, num_1[64]={0};
const int mod = 1e9+7;

int main(){
	int n;
	scanf("%d", &n);
	
	f[0]=1;
	for(int i=1; i<64; i++)
		f[i] = (f[i-1]<<1)%mod;
	//<<優先順序小於 mod
	
	for(int i=0; i<n; i++){
		ll tmp;
		scanf("%lld", &tmp);
		for(int j=0; j<64; j++)
			num_1[j] += (tmp>>j)&1;
	}
	
	for(int i=0; i<64; i++){
		ll one=num_1[i], zero=n-num_1[i], k; 
		//0 0 1
		if(zero>=2 && one>=1){ 
            k = zero*(zero-1)*one/2%mod;
            ans = (ans+k*f[i]%mod)%mod;
        }
        //1 1 1
        if(one>=3){
            k = one*(one-1)*(one-2)/6%mod;
            ans = (ans+k*f[i]%mod)%mod;
        }
	}
	//優先順序 : 
	//'*'、'/' > '+'、'-' > '%' > '<<'、'>>' > '=' 
	printf("%lld", ans);
	
	return 0;
} 

總結

位操作對於異或、與、或等位運算是十分有效的。

相關文章