前缀和:
一维:
原数组:a1,a2,a3,……,an;
前缀和:si=a1+a2+a3+……+ai;
求法:s[i]=s[i-1]+a[i];
作用:快速求出一段的和
代码:
#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;
}
二维:
二维
一个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])
则 (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)
代码:
#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]
代码:
#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;
}
二维:
给定原矩阵a[i,j],构造差分矩阵b[i,j],使得a[,]是b[,]的二维前缀和
则:b[x][y]为格子,a[x][y]为区域
差分核心操作:给以(x1,y1)为左上角,以(x2,y2)为右下角的所有子矩阵中的所有数a[i,j],加上C
对差分数组的影响:
b[x1,y1]+=C =>所有包含b[x1,y1]的加C(青色区)
b[x1,y2+1]-=C =>所有含b[x1,y2+1]的减C(蓝色区域)
b[x2+1,y1]-=C =>所有含b[x2+1,y1]减C(红色区域)
b[x2+1,y2+1]+=C <= 因为粉色区域被加一次,被减两次,所以再加一次
代码:
#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;