Home_W的位运算(位运算+预处理)
Home_W的位运算1
题目链接: 传送门
解题思路:这题有两种解题思路,一种就是\(n^2\times m\)的时间复杂度,还有一种就是经过预处理的时间复杂度为\(n^2\)的方法,先说第一种,大家直接按照题目要求的来,一行向量一行向量的进行比较久能AC,没有卡时间,先贴代码:
code:
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 200;
int mp[N][N];//mp记录的是n个向量
int m,n;
bool check(int a,int b) {
for(int i = 1;i <= m; ++i) {
if(mp[a][i] && mp[b][i])//按照题目要求的进行与,当然你也可以用一个&,
return true; //因为此时的mp记录的是0,1离散值,所以两种判断都行
}
return false;
}
int main()
{
int t;
while(~scanf("%d",&t)) {
while(t--) {
scanf("%d%d",&n,&m);
for(int i = 1; i <= n; ++i) {
for(int j = 1;j <= m; ++j) {
scanf("%d",&mp[i][j]);
}
}
int res = 0;
for(int i = 1;i < n; ++i) {
for(int j = i + 1; j <= n; ++j) {
if(check(i,j)) {
res ++;
}
}
}
printf("%d\n",res);
}
}
return 0;
}
第二种思路就有意思了,我们可以通过向量的这些离散值,把每个向量看成二进制,然后把他还原成一个整数然后再用&进行判断,举个栗子,101B和010B,这两个二进制数,我们要判断他们两个是否有关系,我们可以直接从第一位开始与,一直到最后,我们发现这两个数没有关系,这不正是与运算(&)的运算法则吗,所以我们不需要手动的去从第一位一直比较到最后一位,直接把二进制转为十进制保存,然后\(n^2\)的判断,此时的你一定发现了,向量的维度最大时100的,long long是装不下的,嗯,此时需要一个大数,在这里可以使用C++内置的__int128这个数据类型,他能存储128位(二进制)的整数,但是输入输出需要自己手写,不过此题不用
code:
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 105;
__int128 key[N];
int mp[N];
int m,n;
int main()
{
int t;
while(~scanf("%d",&t)) {
while(t--) {
scanf("%d%d",&n,&m);
for(int i = 1; i <= n; ++i) {
for(int j = 1;j <= m; ++j) {
scanf("%d",&mp[j]);
}
__int128 ans = 0;
for(int j = m; j > 0; --j) {//这里进行预处理,把二进制转化为十进制
ans += mp[j] * (1 << (m - j));
}
key[i] = ans;
}
int res = 0;
for(int i = 1;i <= n; ++i) {
for(int j = i + 1; j <= n; ++j) {
if(key[i] & key[j]) {//O(1)的时间复杂度进行比较
res ++;
}
}
}
printf("%d\n",res);
}
}
return 0;
}
Home_W的位运算2
题目链接: 传送门
解题思路:此题和上题,题目类似,只是数据有所变化,如果直接使用上题暴力解法,会T,再加上我们发现向量的维度只有30,也就是告诉我们,将一个向量转化为2进制用int就能存下,思路参见上题
code:
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = 105,M = 35;
bool mp[N][M];
int key[N];
int n,m;
int main()
{
int t;
while(~scanf("%d",&t)) {
while(t--) {
int ans = 0;
scanf("%d%d",&n,&m);
for(int i = 1;i <= n; ++i) {
for(int j = 1;j <= m; ++j) {
scanf("%d",&mp[i][j]);
}
int ans = 0;
for(int j = m; j > 0; --j) {//将向量的二进制转化为十进制
ans =ans + mp[i][j] * (1 << (m-j));
}
key[i] = ans;
}
for(int i = 1;i <= n; ++i) {
for(int j = i + 1; j <= n; ++j) {
for(int k = j + 1; k <= n; ++k) {
if((key[i] & key[j]) && (key[i] & key[k]) &&(key[j] & key[k])) {
ans++;
}
}
}
}
printf("%d\n",ans);
}
}
return 0;
}
Home_W的位运算3
题目链接: 传送门
解题思路:很明显我们会想到先把b的数全都 xor 一遍,然后通过一位一位的比较,对每种情况分类讨论就行,具体请看代码,第二种方法就简单了,有一个数学结论 a^b=c => a = b ^ c
我先贴结论写法的code:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
ll a,b,c;
int main()
{
int t;
while(~scanf("%d",&t)) {
while(t--) {
scanf("%lld%lld",&b,&c);
ll ans = 0;
for(int i = 0;i < b; ++i) {
scanf("%lld",&a);
ans ^= a;
}
printf("%lld\n",c^ans);
}
}
return 0;
}
第二种手动模拟的code:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
ll a,b,c;
vector<int> p1,p2,p3;//p1对应的是异或后的b,p2对应的是c,p3对应的是x
int main()
{
int t;
while(~scanf("%d",&t)) {//多组输入
while(t--) {
scanf("%lld%lld",&b,&c);
ll ans = 0;
for(int i = 0;i < b; ++i) {
scanf("%lld",&a);
ans ^= a;
}
while(ans) {//把b转为二进制
int k = ans % 2;
p1.push_back(k);
ans /= 2;
}
// for(int i = p1.size() - 1; i >= 0; --i) {
// printf("%d%c",p1[i],i==0?'\n':' ');
// }
// puts("---------------------");
while(c) {//把c转为二进制
int k = c % 2;
p2.push_back(k);
c /= 2;
}
// for(int i = p2.size() - 1; i >= 0; --i) {
// printf("%d%c",p2[i],i==0?'\n':' ');
// }
// puts("---------------------");
for(int i = 0, len = max(p2.size(),p1.size());i < len; ++i) {//类似大数模拟的操作只不过大体分为三类,然后再细分情况
if(i + 1 > p1.size()) {
if(p2[i] == 1)
p3.push_back(1);
else
p3.push_back(0);
}
else if(i + 1 > p2.size()) {
if(p1[i] == 1)
p3.push_back(1);
else
p3.push_back(0);
}
else {
if(p2[i] == 1) {
if(p1[i] == 0)
p3.push_back(1);
else
p3.push_back(0);
}
else if(p2[i] == 0) {
if(p1[i] == 0)
p3.push_back(0);
else
p3.push_back(1);
}
}
}
// for(int i = p3.size() - 1; i >= 0; --i) {
// printf("%d%c",p3[i],i==0?'\n':' ');
// }
// puts("---------------------");
ll res = 0;
for(int i = 0, len = p3.size();i < len; ++i) {
res = res + p3[i]*(1<<i);
}
printf("%lld\n",res);
p1.clear();
p2.clear();
p3.clear();//注意清空操作
}
}
return 0;
}
小节:通过这三题我还是有很大的收获,比如长见识了a^b=c 可以把b除过去……,还有就是&运算符也有了更深的理解。
updata:
Home_W的位运算4
原题链接:传送门
解题思路:通过第三题我们可以知道异或有个性质即:\(a^b=c => a=c^b\),所以我们通过对序列进行前缀异或处理,然后我们将每次异或的结果,在vis数组里面++,表示的是连续的异或的值,然后我们从左到右对s^pre[i]的值进行加和,最后除2就行,注意这里我们需要将vis[0]初始化为1,因为可能有前缀异或结果就等于s的情况。
Code:
#include<bits/stdc++.h>
using namespace std;
const int N = 1000005;
int a[N],b[N],n,q,s;
int vis[N*10];
int main()
{
scanf("%d",&n);
vis[0] = 1;
for(int i = 1;i <= n; ++i) {
scanf("%d",&a[i]);
a[i] ^= a[i-1];
vis[a[i]]++;
}
scanf("%d",&q);
while(q--) {
scanf("%d",&s);
int loc;
int ans = 0;
for(int i = 0;i <= n; ++i) {
loc = s ^ a[i];
ans += vis[loc];
}
printf("%d\n",ans/2);
}
return 0;
}