算法(3)前缀和+差分+双指针+位运算

前缀和:

一维:

原数组:a1,a2,a3,……,an;

前缀和:si=a1+a2+a3+……+ai;

求法:s[i]=s[i-1]+a[i];

作用:快速求出一段的和

练习题:795. 前缀和 - AcWing题库

代码:

#include<iostream>

using namespace std;

const int N = 100010;

int q[N];//存数
int n, m;//n个数m个询问
int s[N];//存前缀和
int res[N];//存答案

int main()
{
	cin >> n >> m;
	s[0] = 0;
	for (int i = 1; i <= n; i++)//i=1开始
	{
		cin >> q[i];
		s[i] = s[i - 1] + q[i];//存前缀和
	}
	for (int i = 0; i < m; i++)
	{
		int l, r;
		cin >> l >> r;
		res[i] = s[r] - s[l - 1];//存m个答案
	}
	for (int i = 0; i < m; i++) cout << res[i] << endl;

	return 0;
}

二维:

二维04f5570abb9046ceb81432d99ce6405e.png

一个q[i][j]就是一个格子,一个s[i][j]就表示如图的黄色区域(注:上边和左边还有为0的看不见的格子),则黄色格子(s[i][j])=蓝色格子(s[i-1][j])+绿色格子(s[i][j-1])+红色格子(a[i][j])-褐色格子(s[i-1][j-1])

40429ee4585041ecb9b3cb96ea1419a5.png

47afda70c94044608e052ebde7a95d33.png

feff57d804d94bc29d14e65f5ae01ee1.png 357952cd2a6e4bf9b7d8b5a04f5531c6.png则        (s[x2][y2]-s[x1][y1])(区域块)     =S[x2,y2]-S[x2,y1-1]-S[x1-1,y2]+S[x1-1,y1-1];

左上(x1,y1) 右下(x2.y2)

练习题:796. 子矩阵的和 - AcWing题库

代码:

#include<iostream>

using namespace std;

const int N = 1005;

int x[N][N];
int n, m, q;
int s[N][N];
int res[N];

int main()
{
	cin >> n >> m >> q;
	for (int i = 0, j = 0; j <= m; j++) s[i][j] = 0;
	for (int i = 0, j = 0; i <= n; i++) s[i][j] = 0;

	for (int i = 1; i <= n; i++)
	{
		for (int j = 1; j <= m; j++)
		{
			cin >> x[i][j];
			s[i][j] = s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1] + x[i][j];
		}
	}
	for (int i = 0; i < q; i++)
	{
		int x1, y1, x2, y2;
		cin >> x1 >> y1 >> x2 >> y2;
		//res[i] = s[x2][y2] - s[x1 - 1][y2] - s[x2][y1 - 1] + s[x1 - 1][y1 - 1];
		cout << s[x2][y2] - s[x1 - 1][y2] - s[x2][y1 - 1] + s[x1 - 1][y1 - 1] << '\n';
	}
	//for (int i = 0; i < q; i++) cout << res[i] << '\n';
	//cout << endl;
	return 0;
}

差分:

一维:

原:a1,a2,a3,···········an;

构造:b1,b2,b3,·······bn;

其中:ai=b1+b2+b3+···········+bi

即差分是前缀和的逆运算

则:b0+b1=a1        =>        b1=a1-b0        =>        b[i]=a[i]-b[i-1]

练习题:797. 差分 - AcWing题库

代码:

#include<iostream>

using namespace std;

const int N = 1000010;

int n, m;
int a[N], b[N];

int main()
{
	cin >> n >> m;
	for (int i = 1; i <= n; i++)
	{
		cin >> a[i];
		b[i] = a[i] - a[i - 1];
	}

	for (int i = 1; i <= m; i++)
	{
		int l, r, c;
		cin >> l >> r >> c;
		b[r + 1] -= c;
		b[l] += c;
	}
	a[1] = b[0] + b[1];
	for (int i = 1; i <= n; i++)
	{
		a[i] = a[i - 1] + b[i];
		cout << a[i] << ' ';
	}
	cout << endl;

	return 0;
}

二维:

题:798. 差分矩阵 - AcWing题库

给定原矩阵a[i,j],构造差分矩阵b[i,j],使得a[,]是b[,]的二维前缀和

则:b[x][y]为格子,a[x][y]为区域

差分核心操作:给以(x1,y1)为左上角,以(x2,y2)为右下角的所有子矩阵中的所有数a[i,j],加上C

39a2ee1d18854e9e9113008deddbf111.png

对差分数组的影响:

b[x1,y1]+=C                =>所有包含b[x1,y1]的加C(青色区)

1716aefe2d544707adbf59ba09fcfba6.png

b[x1,y2+1]-=C                =>所有含b[x1,y2+1]的减C(蓝色区域)

38149c27f7b844a8a4e3ebdc6a21ffea.png

b[x2+1,y1]-=C                =>所有含b[x2+1,y1]减C(红色区域)

fb222e5879be4ba59f18dca9e2f8f68f.png

b[x2+1,y2+1]+=C                <=        因为粉色区域被加一次,被减两次,所以再加一次

aaf21b0e69bc4a1799ae055af59012a8.png代码:

#include<iostream>

using namespace std;

const int N = 1010;

int n, m, q;
int a[N][N], b[N][N];

//对某个区域进行差分更改所需的操作
void insert(int x1, int y1, int x2, int y2, int c)
{
	b[x1][y1] += c;
	b[x2 + 1][y1] -= c;
	b[x1][y2 + 1] -= c;
	b[x2 + 1][y2 + 1] += c;
}

int main()
{
	cin >> n >> m >> q;
	for (int i = 1; i <= n; i++)
	{
		for (int j = 1; j <= m; j++)
		{
			cin >> a[i][j];
		}
	}

	//对一个单位格进行更改。假设原数组为0,则其差分为0,生成a对应的差分数组b
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			insert(i, j, i, j, a[i][j]);

	//对指定区域的差分数组b进行更改
	while (q--)
	{
		int x1, y1, x2, y2, c;
		cin >> x1 >> y1 >> x2 >> y2 >> c;
		insert(x1, y1, x2, y2, c);
	}

	//对b用前缀和求a
	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= m; j++)
			a[i][j] = a[i - 1][j] + a[i][j - 1] - a[i - 1][j - 1] + b[i][j];
	for (int i = 1; i <= n; i++)
	{
		for (int j = 1; j <= m; j++) cout << a[i][j] << ' ';
		cout << '\n';
	}

	return 0;
}

双指针:

双指针的本质是优化双重循环

练习题1(注:已经排好序):799. 最长连续不重复子序列 - AcWing题库

代码:

#include<iostream>

using namespace std;

const int N = 100010;

int n;
int a[N], s[N];

int main()
{
	cin >> n;
	for (int i = 0; i < n; i++) cin >> a[i];

	int res = 0;
	for (int i = 0, j = 0; i < n; i++)
	{
		//记录一个数的个数
		s[a[i]]++;
		//出现重复数时进入,直到j=i或i和j到达该数的最后一个
		while (j <= i && s[a[i]] > 1)
		{
			//剔除原长,找到重复数的最后一个
			s[a[j]]--;
			//移动j
			j++;
		}
		//在不进入while时,不断更新无重复数的长度
		res = max(res, i - j + 1);
	}
	cout << res << endl;

	return 0;
}

位运算:

1.位运算分为逻辑位运算符和位移位运算符

【《算法零基础入门》位运算-哔哩哔哩】

逻辑位运算符:

位与:&

​ 两个数位与运算(&),先转二进制,从低到高按位运算,对于每个位而言,只有当两个位都为1时结果才为1,否则为0,在二进制转十进制,得到结果

位或:|

​ 两个数位或运算(|),先转二进制,从低到高按位运算,对于每个位而言,只有当两个位都为0时结果才为0,否则为1,在二进制转十进制,得到结果

异或:^

​ 两个数异或运算(^),先转二进制,从低到高按位运算,对于每个位而言,只有当两个位都为不同时结果才为1,否则为0,在二进制转十进制,得到结果

按位取反:~

​ 一元运算符,先转二进制,再把0->1, 1->0

位移运算符:

左移:x<<y

​ 先将下x转换为二进制,再向左移动y位,末尾补y位0,左移一位,可看成*2

右移:>>

​ 先将下x转换为二进制,再向右移动y位,(非)负数,高位补(0)1,,右移一位,可看成除2,且向下取整

练习题

2的幂:

​ 1.x>0

​ 2.如果是2的幂,则其二进制必然为10000等,其减1为:011111等,则10000&01111 = 0

​ 3.所以:

if(x > 0 && x & (x - 1) == 0) return ture;

4的幂:

​ 1.4的幂必然是2的幂

​ 2.2的偶数次幂mod3=1;2的奇数次幂mod3=2(初等数论的同余问题)

​ 3.所以

if(x > 0 && x&(x -1)==0 && x%3==1) return ture;

位1的个数:

​ 一个数的二进制为:xxxxxxxxxxxxxx101000000

​ 则其减1为: xxxxxxxxxxxxxx100111111

​ 双方经位与运算后:xxxxxxxxxxxxxx100000000

​ 然后记录一个1

​ 所以代码为:

int n;
cin>>n;
int cnt=0;
while(n)
{
    n & (n-1);
    cnt++;
}
cout<<cnt<<endl;

在不引入第三个变量的情况下,交换ab的值:

方法1: 加法和乘法一样可能会爆int

int a,b;
cin>>a>>b;
a=a+b;
b=a-b;
a=a-b;
cout<<a<<' '<<b<<endl;

方法2:

int a,b;
cin>>a>>b;
a=a^b;
b=a^b;
a=a^b;
cout<<a<<' '<<b<<endl;

​ 任何相同的数x :x^x=0

​ 任何数x:x^0=x

​ 则b=a^b^b=a^0=a;

​ 则a=a^b=(a^b)^a=0^b=b;

给定一个非空整数数组,除了某个数字只出现一次以外,其余数字均出现两次。找出那个只出现一次的数字:

​ 任何相同的数x :x^x=0

​ 任何数x:x^0=x

int n;
cin>>n;
int ans=0;
for(int i=0;i<n;i++)
{
    int a;
    cin>>a;
    ans=ans^a;
}
cout<<ans<<endl;

汉明距离:两个整数之间的 汉明距离 指的是这两个数字对应的二进制位不同的位置的数目。

给定两个整数x和y,计算并返回它们的汉明距离

​ 异或:相同的变成0,不同的变成1,于是变成了求二进制中1的位数

int a,b;
cin>>a>>b;
int ans=a^b;
int ant=0;
while(ans)
{
    ans=ans&(ans-1);
    ant++;
}
cout<<ant<<endl;

交替位二进制数:给定一个整数n,检查它的二进制表示是不是总是0,1交替出现

​ 方法1:判断00和11

​ 00的十进制位0,11的十进制位3

int a;
cin>>a;
while(a)
{
    if(a&3==3 || a|0==0)//a&3==3 || a&3==0
        return false;
    a>>=1;
}
return ture;

​ 方法2:判断01和10

​ 01的十进制为1,10的十进制为2

int a;
cin>>a;
while(a)
{
    if(a&1==1 || a&2==2)//a^2==3 ||a^1==3
        return ture;
    a>>=1;
}
return false;

找出所有子集的异或总和再求和:给定一个数组,求出每个子级的异或总和,计算并返回这些值相加之和

数组{1,2,3}子集的表示:

​ {1} 100

​ {2} 010

​ {12}110

​ {13}101

const int N=100010;
int n;
int a[N];
​
int main()
{
    cin>>n;
    for(int i=0;i<n;i++) cin>>a[i];
    
    int i,j,ans;
    int sum;
    for(i=0;i<(1<<n);++i)//1<<n表示(二进制)1000,有n个0,即2的n次方,每个i表示子集的存在方式,i<(1<<n)即i最大可取111
    {
        for(j=0;j<n;++j)//遍历整个大数组的每一位,或空(该位不存在,i为0),或有(该位存在,为1)
        {
            if(i&(1<<j))//如果1,2,3为在子集i有记为1,则进入异或计算
            {
                ans=ans^a[j]
            }
            sum+=ans;
        }
    }
    cout<<sum<<endl;
    return 0;
}

整数之和:给定两个正整数,要求不使用加号和减号,实现两者之和

异或:同位相同则为0,不同则为1 => 不带进位的加法

位与后左移1位:(a&b)<<1 => 进位储存

当进位储存为0时,加法计算结束

建议自己使用笔算随便计算两个数

#include<iostream>
using namespace std;
​
int getSum(int a,int b)
{
    return b==0? a: getSum(a^b,(a&b)<<1);
}
​
int main()
{
    int x,y;
    cin>>x>>y;
    cout<<getSum(x,y)<<endl;
    return 0;
}

插入:

例如:n=1010101111

​ m=101

将m插入n对应的i~j位,不够的用0补上i=2,j=5

变成:n=1010010111

1.先把n的 i ~j 位归0:将n与1110111(j个位,第k个为0,k从 i 开始到 j 遍历)位与,则第k位归0

2.再把m左移i位,与n异或计算

int a,b,i,j;
cin>>a>>b>>i>>j;
for(long long k=i;k<j;k++)
{
    a=a& ~(1<<k);//01111(k个1)
}
b<<i;
int ans;
ans=a|b;
cout<<ans;

 

 

  • 19
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值