双指针算法:强调单调性
先上模版(最长连续不重复子序列)
给定一个长度为 n 的整数序列,请找出最长的不包含重复的数的连续区间,输出它的长度。
:
const int s = 1e6 + 10;
int j[s], k[s]; //j是原数组,k数组是存储各种数出现的次数
int n, ans;
int main()
{
ios::sync_with_stdio(false);
cin >> n;
for (int i = 0; i < n; i++) cin >> j[i];
for (int r = 0, l = 0; r < n; r++) //设置了l r左右指针
{
k[j[r]]++; //r每向右移,l,r围成的区间就多包一个数,这个数的次数要加一
while (l < r && k[j[r]]>1) //l要向右移动至该数出现的次数小于等于1
{
k[j[l]]--; //l每右移一位,区间就少了一个数,减去该数次数
l++;
}
ans = max(ans, r - l + 1); //取最大值为答案
}
cout << ans;
}
单调性:每次r向右移,l只可能往右移。易证
另外一个例子:
给定两个升序排序的有序数组 A和 B,以及一个目标值 x。
数组下标从 00 开始。
请你求出满足 A[i]+B[j]=x 的数对 (i,j)
数据保证有唯一解。
const int s = 1e5 + 10;
int n, m, x;
int j[s], k[s];
int main()
{
ios::sync_with_stdio(false);
cin >> n >> m >> x;
for (int i = 0; i < n; i++)
{
cin >> j[i];
}
for (int i = 0; i < m; i++)
{
cin >> k[i];
}
for (int dd = 0, ss = m - 1; dd < n; dd++) //两指针一个指头,一个指尾
{
while (j[dd] + k[ss] > x) ss--; //大了,ss指针往前走
if (j[dd] + k[ss] == x)
{
cout << dd << ' ' << ss; break;
}
}
}
单调性:当dd往右移即就j【dd】增大时,ss只可能不动或向左移。
摩多摩多:
给定一个长度为 n 的整数序列 a1,a2,…,an 以及一个长度为 m 的整数序列 b1,b2,…,bm
请你判断 a 序列是否为 b序列的子序列。
子序列指序列的一部分项按原有次序排列而得的序列,例如序列 {a1,a3,a5} 是序列 {a1,a2,a3,a4,a5}的一个子序列。
const int N = 1e5 + 10;
int a[N], b[N];
int n ,m;
int main()
{
cin >> n >> m;
for(int i = 0; i < n; i++) cin >> a[i];
for(int i = 0; i < m; i++) cin >> b[i];
for(int i = 0, j = 0; i < n; i++)
{
while(j < m && a[i] != b[j]) j++;
if(j == m)
{
puts("No");
return 0;
}
j++;//j需要移动到下一位才能够和i++匹配
}
puts("Yes");
return 0;
}
单调性:i指针每往右移动,j也一定往右移。
总之,双指针是一种思想,在多处都可以用到(如快排和归并排序),两指针的行动要有相互关系,注意题目中的单调性
位运算
这里只讲一点
给定一个长度为 n 的数列,请你求出数列中每个数的二进制表示中 11 的个数。
int n, ans;
int lowbit(int x)
{
return (x & -x);
}
int main()
{
ios::sync_with_stdio(false);
cin >> n;
while (n--)
{
int x; cin >> x;
ans = 0;
while (x)
{
x -= lowbit(x);
ans++;
}
cout << ans << ' ';
}
}
lowbit函数返回的是一个数的二进制形式,从右往左的第一个1所带的序列。如x=1101000,返回的值是1000。
实现方法:x&(~x+1),~是反码,而~x+1刚好是-x(即x的补码)
如x=1101000,~x=0010111,~x+1=0011000(可以发现原数x从右往左第一个1的位置在~x+1中变成1)x&(~x+1)=0001000=1000
离散化
将稀疏的数利用一一映射的方式集中,减少占用空间,在数分布的范围广,数却不多稀疏的情况下很有用
假定有一个无限长的数轴,数轴上每个坐标上的数都是 0。
现在,我们首先进行 n 次操作,每次操作将某一位置 x上的数加 c。
接下来,进行 m 次询问,每个询问包含两个整数 l 和 r,你需要求出在区间 [l,r] 之间的所有数的和。
原题x、l、r的范围很大,但n m较小
j, k; 注意点:离散化不改变数组原来的大小顺序,因此这题使用很合适。 区间合并 用了贪心的思想,将各区间的最左端进行排序后向右讨论 给定 n个区间 [l,r],要求合并所有有交集的区间。 注意如果在端点处相交,也算有交集。 输出合并完成后的区间个数。 例如:[1,3][1,3] 和 [2,6][2,6] 可以合并为一个区间 [1,6][1,6]。 typedef pair j, ans; merge(vector & j) tmp; 持续更新中~~~
const int s = 300010;
vector
typedef pair
vector
int a[s], b[s];
int n, m;
int find(int x) //作用是找到数的映射对应的数值,用二分
{
int l = 0, r = alls.size() - 1;
int mid;
while (l < r)
{
mid = (l + r) >> 1;
if (alls[mid] == x) { r = mid; break; }
if (alls[mid] >= x) r = mid;
else l = mid + 1;
}
return r + 1; //返回加一,方便前缀和
}
int main()
{
cin >> n >> m;
for (int i = 0; i < n; i++)
{
int x, v;
cin >> x >> v;
j.push_back({ x,v });
alls.push_back(x); //@
}
for (int i = 0; i < m; i++)
{
int l, r;
cin >> l >> r;
k.push_back({ l,r });
alls.push_back(l);
alls.push_back(r); //和@一起都放入,坐标上的都要映射(加的值v就不用)
}
sort(alls.begin(), alls.end()); //要先排序,映射不能改变顺序(sort优先用pair第一个数排序)
alls.erase(unique(alls.begin(), alls.end()), alls.end()); //unique函数去重复的数
for (auto i : j)
{
int lx = find(i.first); //find函数找映射值
a[lx] += i.second;
}
for (int i = 1; i <= alls.size(); i++) b[i] = b[i - 1] + a[i]; //创建前缀和数组
for (auto i : k)
{
int dl = find(i.first), dr = find(i.second);
int ans = b[dr] - b[dl - 1]; //使用前缀和数组
cout << ans << endl;
}
}
vector
int n;
vector
{
vector
int st = -2e9, ed = -2e9; //设定为无穷左的数,使第一次判断一定成立,
sort(j.begin(), j.end());
for (auto i : j)
{
if (ed < i.first) //如果不相接就新开区间
{
if (st != -2e9) tmp.push_back({ st,ed }); //加判断,防止初始的区间加入
st = i.first, ed = i.second;
}
else ed = max(ed, i.second); //如果相接,就取最长尾,更新现有区间
}
if (st != -2e9) tmp.push_back({ st,ed }); //单独放最后的区间,加判断不要放初始区间
return tmp;
}
int main()
{
cin >> n;
for (int i = 0; i < n; i++)
{
int l, r;
cin >> l >> r;
j.push_back({ l,r });
}
ans = merge(j);
cout << ans.size();
}
如果(肯定有,有不妥之处请指出,看顺眼的话请点个赞吧~~~