常见技巧精选.尺取法(滑动窗口) poj3061 3320 2566 2739 2100

算法概览:

尺取法又常称滑动窗口, 这个技巧常用在求解连续序列里的一些信息,并且在求这些信息的过程,要使这些信息在序列里具有单调性,具体做法通常是指对数组保存一对下标(起点,终点),然后根据实际情况交替推进两个端点得出答案。

比如poj3061求出总和不小于S的连续子序列的长度的最小值。 然后这个算法就像一本尺子一样保持一个宽度,从左往右扫一遍这个序列。

思想很简单,总结这问题特殊的是要找到窗口滑动的停止条件以及怎么实现它。

具体实现也大同小异,我喜欢写法是: 右指针停止的条件就是当前区间符合条件,然后判断一下循环是否要退出,更新一下最优的结果,然后左指针每次向右移动一个。

py 伪代码如下:

l=0 ; r= 0; #左右指针下标
while True:
    while 区间不符合:
        r +=1
        ......
    if 找不到更好解: break
    ans = min(r - l +1, ans) #更新一下最优的结果
    l +=1

for(;;){
   while(...&&r<n){//满足的条件
        
    }
    if() .....
    if(尺取结束条件) break;
    l++;
}

题目:

Subsequence poj3061

#include<cstdio>
#include<algorithm>
using namespace std;
const int N=100000+10;
int a[N];
int main(){
  int T;scanf("%d",&T);
  while(T--){
    int n,s;scanf("%d %d",&n,&s);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    int ans=n+1,i=0,j=0,sum=0;
    for(;;){
        while(sum<s&j<n){
          sum+=a[++j];
        }
        if(sum<s) break;
        ans=min(ans,j-i);
        sum-=a[++i];
    }
    if(ans==n+1) ans=0;
    printf("%d\n",ans);
  }

Jessica’s Reading poj3320

#include<set>
#include<map>
#include<cstdio>
using namespace std;
set<int>cou;
map<int,int>num;//知识点->出现次数的映射
int a[1000000+10];

int main(){
   int n;
   while(~scanf("%d",&n)){
       num.clear();cou.clear();
       for(int i=0;i<n;i++){
          int x;scanf("%d",&a[i]);
       }
       for(int i=0;i<n;i++){
          cou.insert(a[i]);
       }
       int l=0,r=0,t=0,ans=n;
      //尺取法求解
       for(;;){
           while(r<n&&t<cou.size()){
              if(num[a[r++]]++==0) t++;
           }
           if(t<cou.size()) break;
           ans=min(r-l,ans);
           if(--num[a[l++]]==0) t--;
        }
       printf("%d\n",ans);
   }
}

poj2566(变形的尺取法)
题意:
给你一个序列,k次询问每次给一个数,问序列里那段区间和的绝对值最接近询问的数。

思路:很容易想到预处理一个前缀和,但是前缀和不单调,所以需要先排个序。之后就是尺取法了:首尾分别逐步向前挪动,挪动过程中记录答案。

代码:

#include<cstring>
#include<cstdio>
#include<algorithm>

using namespace std;
typedef pair<int,int>P;
P p[100000+10];
int main()
{
    int n,t;
    while(~scanf("%d%d",&n,&t)&&n&&t){
        p[0]=P{0,0};
        int sum=0;
        for(int i=1;i<=n;i++){
           int a;scanf("%d",&a);
           sum+=a;
           p[i]=P{sum,i};
        }
        sort(p,p+n+1);
        for(int i=0;i<t;i++){
            int x;scanf("%d",&x);
            int l=0,r=1,num=0,ans=0x7fffffff,ansl=0,ansr=0;
            while(l<n&&r<=n){
                 int tem=p[r].first-p[l].first;
                 if(abs(tem-x)<abs(ans-x)){
                      ans=tem;ansl=p[l].second;ansr=p[r].second;
                 }
                 if(tem==x) break;
                 else if(tem<x) r++;
                 else l++;
                 if(l==r) r++;
            }
            if(ansl>ansr) swap(ansl,ansr);
            printf("%d %d %d\n",ans,ansl+1,ansr);
        }
    }
}

poj2739
题意:给你一个数,判断能不能由一段连续素数和构成。
先打一个素数表,再尺取法。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=10000+10;
bool prime[maxn];
int num[1500],t;
void solve()
{
    memset(prime,true,sizeof(prime));
    prime[0]=prime[1]=false;
    for(int i=2;i<=sqrt(maxn)+1;i++){
        if(prime[i]){
            for(int j=i*2;j<=10000;j+=i){
                prime[j]=false;
            }
        }
    }
    t=0;
    for(int i=2;i<=10000;i++){
      if(prime[i]) num[t++]=i;
    }
}

int main()
{
    solve();
    int n;
    while(scanf("%d",&n)&&n){
       int ans=0,l=0,r=0,tem=0;
       for(;;){
          while(tem<n&&num[r]<=n){
            tem+=num[r++];
          }
          if(tem==n) ans++;
          tem-=num[l++];
          if(l==r) break;
       }
       printf("%d\n",ans);
    }
}

poj2100
和上一题类似,问一个数能不能由一段来连续的序列平方和构成。

#include<cstdio>
using namespace std;
typedef long long ll;

int main(){
    ll n;
    while(~scanf("%lld",&n)){
       ll ans=0,l=1,r=1,num=0;
       for(;;){
          while(num<n&&r*r<=n){
             num+=r*r;
             r++;
          }
          if(num==n) ans++;
          if(l*l>=n) break;
          num-=l*l;l++;
       }
       printf("%lld\n",ans);
       num=0;l=1;r=1;
       for(;;){
          while(num<n&&r*r<=n){
             num+=r*r;
             r++;
          }
          if(num==n){
             printf("%lld",r-l);
             for(int i=l;i<r;i++) printf(" %d",i);
             printf("\n");
          }
          if(l*l>=n) break;
          num-=l*l;l++;
          
       }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值