方法引入
有一组数,现在给定一个数k,问这组数中两个数加和为k的情况有多少?
- 考虑暴力,显然可以使用两个for循环,时间复杂度为O(n2),对于n在104以上的数据无法在规定时间内得到答案,需要寻找更好的方法
- 因为顺序不影响答案,所以考虑先排个序,要的是两个数加和,所以需要两个指针,分别指向这两个数,可以把两个指针分别指向数组头和尾,如果两个数加和比k大,那么显然需要将尾指针向前移动一位;如果比k小,显然需要将头指针向后移动一位;相等的时候即为答案
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <string>
#include <cmath>
#include <iomanip>
#include <queue>
#include <stack>
#include <map>
using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f;
const int MAXN = 2e5+100;
int Data[MAXN];
int main(){
int n,sum;
cin>>n>>sum;
for(int i=1;i<=n;i++) scanf("%d",Data + i);
int i = 1,j = n;
int ans = 0;
while(i < j){
if(Data[i] + Data[j] < sum){
j++;
}
else if(Data[i] + Data[j] > sum){
i++;
}
else{
ans++;
i++;
j--;
}
}
return 0;
}
方法总结
- 这就是一种尺取法的表现方式,尺取法不一定是非要从前往后,也有可能是两边到中间,具体问题需要具体分析,还要注意问题能否使用尺取法,一般区间上的问题就可能要使用尺取法
具体问题
模板题
- 给定一个数s,求一个连续子序列,让它的和大于等于s,问有多少个这样的连续子序列
- 显然问题和数组元素位置是有关系的,那么可以考虑从前往后推导,开始时候双指针位置都在第一个,如果两指针区间和小于s,那么右指针右移;否则区间和更新,左指针右移,直至r到达n
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <string>
#include <cmath>
#include <iomanip>
#include <queue>
#include <stack>
#include <map>
using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f;
const int MAXN = 2e5+100;
int Data[MAXN];
int main(){
int t, s, n;
cin>>t;
while(t--){
scanf("%d%d",&n, &s);
for(int i=0;i<n;i++) scanf("%d", Data + i);
int l, r;
l = r = 0;
int sum = 0;
int ans = INF;
while(r <= n){
if(sum < s){
sum += Data[r];
r++;
}else{
ans = min(ans, r - l);
sum -= Data[l];
l++;
}
}
ans == INF?printf("0\n"):printf("%d\n", ans);
}
return 0;
}
最大子段和
最大子段和
求一组数的最大子段和
- 这其实是一个动态规划的问题,子段可以从前往后dp进行,如何选择当前元素取决于该元素的前缀和和它本身之间的大小关系,如果前缀和比它大,那么更新sum为前缀和;否则更新sum为当前元素
- 有状态转移方程dp[i] = max(dp[i - 1] + Data[i], Data[i]),程序可使用尺取法思想编写也就是右指针向右移动,不停更新sum(sum表示前缀和),这里面判断条件为sum + Data[i] < Data[i]化简即为sum < 0
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <string>
#include <cmath>
#include <iomanip>
#include <queue>
#include <stack>
#include <map>
using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f;
const int MAXN = 2e5+100;
int Data[MAXN];
int main(){
int n;
scanf("%d",&n);
for(int i=0;i<n;i++) scanf("%d",Data + i);
int sum = 0, ans = -INF;
for(int i=0;i<n;i++){
if(sum < 0) sum = Data[i];
else sum += Data[i];
if(sum > ans){
ans = sum;
}
}
cout<<ans;
return 0;
}
- 给出一个环形序列,求序列中最大连续子序列
- 分两种情况考虑,其一是去环的最大连续子序列;其二是带环的最大连续子序列,第一种情况就是最大子段和;第二种情况可以考虑正难则反,找到最小子段和,用数列和减去它即为答案,最后取大值
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <string>
#include <cmath>
#include <iomanip>
#include <queue>
#include <stack>
#include <map>
using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f;
const int MAXN = 2e6+100;
int Data[MAXN];
int main(){
int t,n;
scanf("%d",&t);
while(t--){
scanf("%d",&n);
int ans1 = -INF,ans2 = INF;
int r = 0;
int sum = 0;
int tot = 0;
for(int i=0;i<n;i++){
scanf("%d",Data + i);
tot += Data[i];
}
while(r < n){
if(sum + Data[r] < 0) sum = 0;
else{
sum += Data[r];
ans1 = max(ans1, sum);
}
r++;
}
sum = 0;
r = 0;
while(r < n){
if(sum + Data[r] > 0) sum = 0;
else{
sum += Data[r];
ans2 = min(ans2, sum);
}
r++;
}
printf("%d\n", max(ans1, tot - ans2));
}
return 0;
}
- 给出一个数n,使得连续正整数的平方和等于此数,输出所有可能情况
- 考虑尺取,开始时候l和r指针都指向1,sum表示l到r闭区间内区间和,如果此时sum小于n,那么右指针右移;如果大于n,左指针右移;如果相等,跟新sum,左右指针都要向右移动,需要注意的是最后的r要减1
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <string>
#include <cmath>
#include <iomanip>
#include <queue>
#include <stack>
#include <map>
using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f;
const int MAXN = 2e5+100;
int Data[MAXN];
vector<pair<ll, ll>> vs;
int main(){
ll n;
scanf("%lld",&n);
ll l, r;
ll sum = 0;
l = r = 1;
while(l * l <= n){
if(sum < n){
sum += r * r;
r++;
}
else if(sum > n){
sum -= l * l;
l++;
}else{
vs.push_back(make_pair(l, r - 1));
sum -= l * l;
sum += r * r;
l++;
r++;
}
}
int num = vs.size();
printf("%d\n", num);
for(int i=0;i<num;i++){
printf("%lld ", vs[i].second - vs[i].first + 1);
for(ll j=vs[i].first;j<=vs[i].second;j++){
printf("%lld ",j);
}
printf("\n");
}
return 0;
}