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;
}
posted @ 2021-01-16 21:58  MangataTS  阅读(192)  评论(0编辑  收藏  举报