尺取法+变通

所谓尺取法,是枚举区间的时候的一种优化思想。

尺取法通常适用于选取区间有一定规律,或者所选取的区间有一定的变化趋势的情况。

一般题目的尺取法比较普通,所以以下例题所用尺取法将比较特别。

 

尺取+逆元

https://ac.nowcoder.com/acm/contest/3005/C

l代表左端点,r代表右端点,当长度=k时,就比较答案,然后l++,r++,始终维持着长度=k,而因为是区间乘积,所以在l++、r++时,我们要把第 l 个元素除去,以及乘上第 r 个元素,而我们当前答案值已经是取模过后的数,所以为防止出现取模后为0的情况,把第 l 个元素除去要用到逆元的方式来处理。

#include <bits/stdc++.h>
#define debug frelimiten("r.txt","r",stdin)
#define mp make_pair
#define ri register int
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
const int maxn = 2e5+10;
const int INF = 0x3f3f3f3f; 
const int mod = 998244353;
inline ll read(){ll s=0,w=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
while(ch>='0'&&ch<='9') s=s*10+ch-'0',ch=getchar();
return s*w;}
ll qpow(ll p,ll q){return (q&1?p:1)*(q?qpow(p*p%mod,q/2):1)%mod;}
int n,k,i,l,r;
ll sum,a[maxn],ans;
int main()
{
    n=read(),k=read();
    for (i=1;i<=n;i++) a[i]=read();
    sum=1;
    l=r=1;
    ans=-INF;
    while (r<=n)
    {
        if (a[r])
        {
            sum=sum*a[r]%mod;
            if (r-l+1==k)
            {
                ans=max(ans,sum);
                sum=sum*qpow(a[l],mod-2)%mod;
                l++;
            }
        }
        else
        {
            l=r+1;
            sum=1;
        }
        r++;
    }
    cout<<ans<<endl;
    return 0;
}
View Code

 

尺取+线段树

ICPC 2017 Asia Xian K. LOVER II

在此题之前有道简单版的B题,方法只用到了尺取法

#include <bits/stdc++.h>
#define debug freopen("r.txt","r",stdin)
#define mp make_pair
#define ri register int
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
const int maxn = 2e5+5;
const int INF = 0x3f3f3f3f; 
const int mod = 998244353;
inline ll read(){ll s=0,w=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
while(ch>='0'&&ch<='9') s=s*10+ch-'0',ch=getchar();
return s*w;}
ll qpow(ll p,ll q){return (q&1?p:1)*(q?qpow(p*p%mod,q/2):1)%mod;}
int t,i,l;
ll n,k,a[maxn],b[maxn],ans;
int main()
{
    t=read();
    while (t--)
    {
        n=read(),k=read();
        for (i=1;i<=n;i++) a[i]=read();
        for (i=1;i<=n;i++) b[i]=read();
        sort(a+1,a+1+n);
        sort(b+1,b+1+n);
        l=n;
        ans=0;
        for (i=1;i<=n;i++)
        {
            if (b[l]+a[i]>=k)
            {
                ans++;
                l--;
            } 
        }
        cout<<ans<<endl;
    }
    return 0;
}
View Code

而这题因为有个区间询问的附加问题,所以要在尺取法的基础上加上线段树。

怎么加呢?因为在最终的选择序列中,最少需要有 1 个男生与 a[1] 的和大于等于 k,有 2 个男生 与 a[2] 的 和大于等于 k,以此类推,所以我们把每位女生至少所能配对的男生数设为 -4,-3,-2,-1,意思是如果能全部匹配的话,那么,第1位女生一定会有4位男生的和大于等于 k,第2位女生一定会有3位男生的和大于等于 k……,所以当当前区间男生能和所有女生配对的话,那么所有值将会>=0。

我们计算每一位男生做为开头,然后在这名男生之后的男生中,要选多少个才能符合题目条件,所以我们设 l 做为某位男生的开头位置,r做为某位男生的最终位置,而以这位男生做为开头时,至少选到要到第 r位男生才能符合条件。而我们选下一位男生,即第l+1位男生时,先消除第l位男生的影响,然后再判断当前所有是否全部配对,若不全配对,就加入下一位男生r+1,计算这位男生的影响,直到符合题目条件。

#include <bits/stdc++.h>
#define debug freopen("r.txt","r",stdin)
#define mp make_pair
#define ri register int
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
const int maxn = 1e6+10;
const int INF = 0x3f3f3f3f; 
const int mod = 1e9+7;
inline ll read(){ll s=0,w=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
while(ch>='0'&&ch<='9') s=s*10+ch-'0',ch=getchar();
return s*w;}
ll qpow(ll p,ll q){return (q&1?p:1)*(q?qpow(p*p%mod,q/2):1)%mod;}
struct node
{
    ll lazy,minn;    
};
node segment_tree[maxn*6];
struct segment
{
    void push_up(int num)
    {
        segment_tree[num].minn=min(segment_tree[num<<1].minn,segment_tree[num<<1|1].minn);
    }
    void init(int num,int l,int r)
    {
        segment_tree[num].lazy=0;
        if (l==r) 
        {
            segment_tree[num].minn=-l;
            return;
        }
        int mid=(l+r)/2;
        init(num<<1,l,mid);
        init(num<<1|1,mid+1,r);    
        push_up(num);
    }
    void push_down(int num)
    {
        if (segment_tree[num].lazy==0) return;
        segment_tree[num<<1].lazy+=segment_tree[num].lazy;
        segment_tree[num<<1|1].lazy+=segment_tree[num].lazy;
        segment_tree[num<<1].minn+=segment_tree[num].lazy;
        segment_tree[num<<1|1].minn+=segment_tree[num].lazy;
        segment_tree[num].lazy=0;
    }
    void upgrade(int num,int l,int r,int want_zuo,int want_you,ll val)
    {
        if (want_zuo<=l && r<=want_you)
        {
            segment_tree[num].minn+=val;
            segment_tree[num].lazy+=val;
            return;
        }
        int mid=(l+r)/2;
        push_down(num);
        if (want_zuo<=mid) upgrade(num<<1,l,mid,want_zuo,want_you, val);
        if (want_you>mid) upgrade(num<<1|1,mid+1,r,want_zuo,want_you,val);
        push_up(num);
    }
    ll query(int num,int l,int r,int want_zuo,int want_you)
    {
        if (want_zuo<=l && r<=want_you) return segment_tree[num].minn;
        push_down(num);
        ll ans=INF;
        int mid=(l+r)/2;
        if (want_zuo<=mid) ans=min(ans,query(num<<1,l,mid,want_zuo,want_you));
        if (want_you>mid) ans=min(ans,query(num<<1|1,mid+1,r,want_zuo,want_you));
        return ans;
    }
}tree;
int t,m,n,k,i,a[maxn],b[maxn],R[maxn],pos,q,l,r;
int main()
{
    t=read();
    while (t--)
    {
        n=read(),m=read(),k=read();
        for (i=1;i<=n;i++) a[i]=read();
        for (i=1;i<=m;i++) b[i]=read();
        sort(a+1,a+1+n);
        tree.init(1,1,n);
        for (i=1;i<=m;i++)
        {
            R[i]=R[i-1];
            while (segment_tree[1].minn<0)
            {
                R[i]++;
                if (R[i]>m) break;
                pos=lower_bound(a+1,a+1+n,k-b[R[i]])-a;
                if (pos<=n)
                    tree.upgrade(1,1,n,pos,n,1);
            }
            pos=lower_bound(a+1,a+1+n,k-b[i])-a;
            if (pos<=n) tree.upgrade(1,1,n,pos,n,-1);
        }
        q=read();
        while (q--)
        {
            l=read(),r=read();
            if (R[l]<=r) cout<<1<<endl;
                else cout<<0<<endl;
        }
    }
    return 0;
}
View Code

 

posted @ 2020-05-19 22:51  Y-KnightQin  阅读(142)  评论(0编辑  收藏  举报