尺取法及例题

方法引入

有一组数,现在给定一个数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;
}

方法总结

  • 这就是一种尺取法的表现方式,尺取法不一定是非要从前往后,也有可能是两边到中间,具体问题需要具体分析,还要注意问题能否使用尺取法,一般区间上的问题就可能要使用尺取法

具体问题

模板题

poj3061

  • 给定一个数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;
}

poj2100

  • 给出一个数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;
}

猜你喜欢

转载自blog.csdn.net/roadtohacker/article/details/113033473