2025.2.7
双指针
双指针实践上算是一种思想技巧,很灵活。说是双指针,但是我们在实际上使用的时候直接模拟进行就行,不过我们单独使用双指针的时候通常都是需要单调性,不然就是在其他的数据结构上进行。
比如我们需要找出一个数组中,一段1的连续最长长度或者 乘积/和 小于一定值的一定长度是多少。这个样子就是很典型的双指针。然后我们在使用只针对时候主要有快慢左右和对撞这种用法。
然后下面是部分的例子。我个人不喜欢直接用指针的 :(
P1147 连续自然数和
首项加末项乘以项数除以二。发现对于一个起点,往后最接近的终点长度是一定的。我们就可以从前往后枚举区间,暴力显然超时所以双指针很自然的出来了,不过还可以用数学方法优化。
while(l<=r&&r<n)
{
int x=calc(l,r);
if(x==n)
{
cout<<l<<' '<<r<<endl;
r++;
}
else if(x<n)
r++;//小了就得吧区间和扩大,所以右端点往右
else
l++;
}
P1102 A-B 数对
讲实话,这个就是打个闪。我想法优先是映射和二分,然后试了试map就过了 ^^
双指针的做法应该是记录出现册数然后实际去根据C+B=A去枚举计算,然后映射和二分就暴力的很了
for(int i=1;i<=n;i++)
{
cin>>num[i];
maps[num[i]]++;
num[i]-=m;
}
for(int i=1;i<=n;i++)
ans+=maps[num[i]];
cout<<ans<<endl;
P3143 [USACO16OPEN] Diamond Collector S
双指针从左和右边去记录两端的最大值,然后分别记录左端右端最大的差小于等于k的学列,然后最后枚举所有的电
for(int i=2;i<=n;i++)
{
while(a[i]-a[now]>k)
now++;
l[i]=max(l[i-1],i-now+1);
}
now=n;
for(int i=n-1;i>=1;i--)
{
while(a[now]-a[i]>k)
now--;
r[i]=max(r[i+1],now-i+1);
}
for(int i=1;i<n;i++)
{
ans=max(ans,l[i]+r[i+1]);
}
P3029 [USACO11NOV] Cow Lineup S
有没有觉得似曾相识;双指针做法我们肯定是要坐标先排序(确定单调性才能有判断性的去移动指针)
范围从小到大,r在最左端向右一定,然后一直到各个种类都出现了,然后可以记录,然后移动l然后再去重复上述操作,注意我们维护出现次数的时候的加加减减别漏了(
和‘生日礼物’和‘逛画展’都有点像,此外暴力map存种类去写然后也行
P6465 [传智杯 #2 决赛] 课程安排
注意读题去思考里面的限制
while(l<=n&&l<=r)
{
while(a[r]!=a[r+1]&&r<n)
r++;//去找最大的合法右端点
for(int i=l;i<=r;i++)
{
if(i-l+1>=m)
{//长度符合要求
cnt[a[i-m+1]]++; //把头的数字出现次数++
ans+=i-l+1-m+1-cnt[a[i]]; //当前长度减去必需长度剩下的都是能够选的,但是要减去i出现次数,因为首位不能一样
}
}
for(int i=l;i<=r;i++)
cnt[a[i]]=0;//以这个r为终点进行枚举结束所以清零
r++;//然后以r+1为新的起点去寻找
l=r;
}
P1381 单词背诵
是不是和上面的那个拍照也是有点像
while(1)
{
if(!cnt)
{
while(!vis[b[r]])
r--;
ans=min(ans,r-l);
if(flag[b[r]]>=1)
{
if(flag[b[r]]==1)
{
cnt++;
}
flag[b[r]]--;
r--;
}
}
else
{
if(!l)
break;
if(vis[b[l]])
{
if(!flag[b[l]])
{
cnt--;
}
flag[b[l]]++;
}
l--;
}
}

浙公网安备 33010602011771号