不带删尺取小记

前言

期末考考了,当时用倍增+ST表多了个 \(\log\) 做的

适用于双指针删除时复杂度很高,添加时复杂度很低的情形。

大概有两种类型,本质是一样的,只是写法有点区别。


双指针的移动与限制条件有关

CF1548B Integers Have Friends

洛谷传送门
CF1548B


分析

差分后转化为求区间 \(\gcd>1\) 的最长区间长度

有很多做法,带 \(\log n\) 的就是二分+ST表或者线段树+双指针

双指针就是只要区间 \(\gcd=1\) 左指针就不断右移。

继续考虑双指针,如果能将区间分成两段 \([l,mid],[mid+1,r]\)

前一段为后缀 \(\gcd\),后一段为前缀 \(\gcd\),然后就可以直接拼起来,双指针的移动也非常简单。

问题就是怎样判断后缀 \(\gcd\) 在什么时候计算,有一种办法,

就是当 \(l>mid\) 的时候直接将 \([l,r]\) 全部变成后缀 \(\gcd\),然后将 \(l,mid\) 全部赋值为 \(r\)

每个位置最多进行一次前缀 \(\gcd\) 和一次后缀 \(\gcd\),所以复杂度就是 \(O(n\log {a_i})\)


代码

#include <cstdio>
#include <cctype>
#include <algorithm>
using namespace std;
typedef long long lll;
const int N=200011;
int n,l,mid,ans; lll a[N],f[N];
lll iut(){
    lll ans=0,f=1; char c=getchar();
    while (!isdigit(c)) f=(c=='-')?-f:f,c=getchar();
    while (isdigit(c)) ans=ans*10+c-48,c=getchar();
    return ans*f;
}
void print(int ans){
    if (ans>9) print(ans/10);
    putchar(ans%10+48);
}
lll gcd(lll x,lll y){return y?gcd(y,x%y):x;}
int main(){
    for (int T=iut();T;--T){
        n=iut(),l=mid=ans=0,f[0]=a[0]=1;
        for (int i=1;i<=n;++i) a[i]=iut();
        for (int i=1;i<n;++i)
        if (a[i]<a[i+1]) a[i]=a[i+1]-a[i];
            else a[i]-=a[i+1];
        --n;
        for (int i=1;i<=n;++i){
            f[i]=(i-1==mid)?a[i]:gcd(f[i-1],a[i]);
            while (l<=mid&&gcd(f[i],f[l])==1) ++l;//后缀与前缀拼接判断gcd
            if (l>mid){
                f[mid=i]=a[i];
                for (int j=mid-1;j>=l;--j) f[j]=gcd(f[j+1],a[j]);
                while (l<=mid&&f[l]==1) ++l;//重构之后只需要看f[l]
            }
            ans=max(ans,i-l+1);
        }
        print(ans+1),putchar(10);
    }
    return 0;
}

CF1547F Array Stabilization (GCD version)

洛谷传送门
CF1547F


分析

首先要断环为链,非常浅显的做法就是二分+ST表,判断当前判定的次数下是否能够使区间 \(\gcd\) 等于所有数的 \(\gcd\)

可以发现,还是可以用线段树+双指针做,指针移动就变成了不等于所有数的 \(\gcd\)

转化题意后和上一题本质是一样的,翻转数组就差不多了,可以利用不带删尺取


代码

#include <cstdio>
#include <cctype>
#include <algorithm>
using namespace std;
const int N=400011;
int n,l,mid,ans,a[N],f[N];
int iut(){
    int ans=0,f=1; char c=getchar();
    while (!isdigit(c)) f=(c=='-')?-f:f,c=getchar();
    while (isdigit(c)) ans=ans*10+c-48,c=getchar();
    return ans*f;
}
void print(int ans){
    if (ans>9) print(ans/10);
    putchar(ans%10+48);
}
int gcd(int x,int y){return y?gcd(y,x%y):x;}
int main(){
    for (int T=iut();T;--T){
        n=iut(),l=mid=ans=0,a[0]=0;
        for (int i=1;i<=n;++i) a[i]=iut(),a[0]=gcd(a[0],a[i]);
        reverse(a+1,a+1+n),f[0]=a[0];
        for (int i=1;i<=n;++i) a[i+n]=a[i];
        for (int i=1;i<n*2;++i){
            f[i]=(i-1==mid)?a[i]:gcd(f[i-1],a[i]);
            while (l<=mid&&gcd(f[i],f[l])==a[0]) ++l;
            if (l>mid){
                f[mid=i]=a[i];
                for (int j=mid-1;j>=l;--j) f[j]=gcd(f[j+1],a[j]);
                while (l<=mid&&f[l]==a[0]) ++l;
            }
            ans=max(ans,i-l+1);
        }
        print(ans),putchar(10);
    }
    return 0;
}

JZOJ 6358 小ω的仙人掌

题目传送门

小ω有 \(s\) 个物品,每个物品有一定的大小和权值。

她可以从任意第 \(L\) 个物品走到第 \(R\) 个物品,这个区间内的物品可以选或者不选。

她取出的物品大小和必须为 \(w\),权值和必须小于等于 \(k\)

她想知道这个区间最短是多少。如果无解,请输出“-1”,大样例来自 ?

对于 \(100\%\) 的测试点,保证 \(1\leq s\leq 10^4,1\leq b_i\leq 2\times 10^4,1\leq a_i\leq w\leq 5\times 10^3,1\leq k\leq 10^9\)


分析

还是一样考虑双指针,问题是背包删除不好弄,

那就用不带删尺取变成后缀背包和前缀背包的结合。

注意这里求的是最短区间所以要在指针移动时求最小值


代码

#include <cstdio>
#include <cctype>
using namespace std;
const int N=5011;
int dp[N<<1][N],w[N<<1],c[N<<1],n,m,k,l,mid,ans;
int iut(){
    int ans=0; char c=getchar();
    while (!isdigit(c)) c=getchar();
    while (isdigit(c)) ans=ans*10+c-48,c=getchar();
    return ans;
}
int min(int a,int b){return a<b?a:b;}
int main(){
    n=iut(),m=iut(),k=iut(),ans=n+1;
    dp[0][0]=0;
    for (int i=1;i<=m;++i) dp[0][i]=0x3f3f3f3f;
    l=mid=0;
    for (int i=1;i<=n;++i){
        w[i]=iut(),c[i]=iut();
        if (i-1==mid){
            dp[i][0]=0,dp[i][w[i]]=c[i];
            for (int j=1;j<w[i];++j) dp[i][j]=0x3f3f3f3f;
            for (int j=m;j>w[i];--j) dp[i][j]=0x3f3f3f3f;
        }else{
            for (int j=0;j<w[i];++j) dp[i][j]=dp[i-1][j];
            for (int j=w[i];j<=m;++j)
                dp[i][j]=min(dp[i-1][j],dp[i-1][j-w[i]]+c[i]);
        }
        for (;l<=mid;++l){
            int flag=1;
            for (int j=0;j<=m&&flag;++j)
                if (dp[l][j]+dp[i][m-j]<=k) flag=0;
            if (flag) break;
            ans=min(ans,i-l+1);
        }
        if (l>mid){
            mid=i;
            dp[mid][0]=0,dp[mid][w[mid]]=c[mid];
            for (int j=1;j<w[mid];++j) dp[mid][j]=0x3f3f3f3f;
            for (int j=m;j>w[mid];--j) dp[mid][j]=0x3f3f3f3f;
            for (int _i=mid-1;_i>=l;--_i){
                for (int j=0;j<w[_i];++j) dp[_i][j]=dp[_i+1][j];
                for (int j=w[_i];j<=m;++j) dp[_i][j]=min(dp[_i+1][j],dp[_i+1][j-w[_i]]+c[_i]);
            }
            while (l<=mid&&dp[l][m]<=k) ans=min(ans,i-l+1),++l;
        }
    }
    if (ans==n+1) printf("-1");
        else printf("%d",ans);
    return 0;
}

双指针的移动与所求区间有关

OJ 1-F 矩阵滑窗

题目大意

给定 \(n\)\(k\times k\) 的矩阵 \(A_i\),现在有 \(q\) 组询问,每组询问 \(\prod_{o=l_i}^{r_i}A_o\)

对于 \(100\%\) 的测试点满足 \(n\leq 10^5,k\leq 4,q\leq 10^5,\forall j\leq i,l_j\leq l_i,r_j\leq r_i\)


分析

由于区间是锁定的,所以两个指针有一点区别,\(\_l,\_mid\) 取代了原来的 \(mid,r\)

其实也差不多,\(l>\_l\) 的时候就重构,\(\_mid\) 就是右指针的移动

\([l,\_l]\) 维护的是后缀矩阵积,\([\_l,\_mid]\) 维护的是前缀矩阵积


代码

#include <cstdio>
#include <cctype>
#include <algorithm>
using namespace std;
const int N=100011;
int n,m,Q,_l,_mid;
struct Matrix{
    int p[4][4];
    Matrix operator *(const Matrix &T)const{
        Matrix C;
        for (int j=0;j<4;++j)
        for (int k=0;k<4;++k) C.p[j][k]=0;
        for (int i=0;i<m;++i)
        for (int j=0;j<m;++j)
        for (int k=0;k<m;++k)
            C.p[i][j]=(C.p[i][j]+1ll*p[i][k]*T.p[k][j])%919260817;
        return C;
    }
}A[N],B[N],C;
int iut(){
    int ans=0; char c=getchar();
    while (!isdigit(c)) c=getchar();
    while (isdigit(c)) ans=ans*10+c-48,c=getchar();
    return ans;
}
void print(int ans){
    if (ans>9) print(ans/10);
    putchar(ans%10+48);
}
int main(){
    n=iut(),m=iut(),Q=iut();
    _l=_mid=0;
    for (int i=1;i<=n;++i){
        for (int j=0;j<m;++j)
        for (int k=0;k<m;++k)
            A[i].p[j][k]=iut();
    }
    for (int i=1;i<=Q;++i){
        int l=iut(),r=iut();
        Matrix C;
        if (l>_l){
            B[r]=A[r];
            for (int j=r-1;j>=l;--j) B[j]=A[j]*B[j+1];
            _l=r,_mid=r,C=B[l];
        }else{
            if (_l==_mid&&_mid<r)
                ++_mid,B[_mid]=A[_mid];
            while (_mid<r) ++_mid,B[_mid]=B[_mid-1]*A[_mid];
            C=B[l]*B[r];
        }
        for (int j=0;j<m;++j)
        for (int k=0;k<m;++k)
            print(C.p[j][k]),putchar(32);
        putchar(10);
    }
    return 0;
}
posted @ 2024-02-13 02:35  lemondinosaur  阅读(126)  评论(0编辑  收藏  举报