【考试总结】2022-03-13

跑步

暴力 dp[i][j]=max(dp[i-1][j],dp[i][j-1])+a[i][j] 那么在产生 \(a_{i,j}\) 的加减之后在第 \(x\) 行会产生变化,且变化的地方是连续的

计算这些变化对下一行的影响发现下一行的变化位置也是一个连续区间,如果在网格图上画出来这两段必然联通

那么使用树状数组维护 \(\rm DP\) 数组的差分表,使用单调指针判断某段是不是发生变化,不难发现变化的形式一定是和修改一样的 \(\pm1\),可以更新答案

那么实现的时候维护差分表和 \(a_{i,j}\) 用来计算原来/新的 \(\rm DP\) 值即可

Code Display
const int N=2010;
int sum=0,dp[N][N],n,a[N][N];
struct Fenwick_Tree{
    int c[N];
    inline int query(int x){int res=0; for(;x;x-=x&(-x)) res+=c[x]; return res;}
    inline void insert(int x,int v){int res=0; for(;x<=n;x+=x&(-x)) c[x]+=v; return ;} 
}T[N];
signed main(){
    freopen("run.in","r",stdin); freopen("run.out","w",stdout);
    n=read(); 
    rep(i,1,n) rep(j,1,n){
        dp[i][j]=max(dp[i-1][j],dp[i][j-1])+(a[i][j]=read());
        T[i].insert(j,dp[i][j]-dp[i][j-1]);
        sum+=dp[i][j];
    }
    print(sum);
    int Q=n;
    // Maintain T,sum,a
    while(Q--){
        char opt=Getalpha();
        int lin=read(),col=read(),delt=opt=='U'?1:-1;
        a[lin][col]+=delt;
        int nc=col;
        T[lin].insert(col,delt); T[lin].insert(col+1,-delt);
        int lef=col,rig=n;
        for(int i=col+1;i<=n;++i){
            int dpnow=T[lin].query(i),A=T[lin].query(i-1),B=T[lin-1].query(i);
            if(dpnow==max(A,B)+a[lin][i]){
                rig=i-1;
                break;
            }else{
                T[lin].insert(i,delt);
                T[lin].insert(i+1,-delt);   
            }
        }
        sum+=(rig-lef+1)*delt;
        ++lin;
        while(lin<=n){
            int nl=lef,nr=rig;
            //updated positions:[line-1,lef] -> [line-1,rig]
            while(nl<=rig){
                int dpnow=T[lin].query(nl);
                if(dpnow!=max(T[lin].query(nl-1),T[lin-1].query(nl))+a[lin][nl]){
                    T[lin].insert(nl,delt);
                    break;
                }
                ++nl;
            }
            if(nl>nr) break;
            T[lin].insert(nr+1,-delt);
            while(nr<n){
                int dpnow=T[lin].query(nr+1),A=T[lin].query(nr),B=T[lin-1].query(nr+1);
                if(dpnow==max(A,B)+a[lin][nr+1]) break;
                else{
                    ++nr;
                    T[lin].insert(nr,delt);
                    T[lin].insert(nr+1,-delt);
                } 
            }
            rig=nr; lef=nl;
            sum+=(rig-lef+1)*delt;
            lin++;
        }
        print(sum);
    }
    return 0;
}

算术

我赛时背过并猜测 二次剩余的判别准则的推广 是正确的从而得了 \(100\)

考虑 二次剩余判别准则:对于奇素数 \(p\) 如果满足 \(n^{\frac{p-1}2}\equiv 1\mod p\) 那么 \(n\) 是模 \(p\) 意义下的二次剩余

证明

从两者等价的角度来考虑这个问题:

  • 如果 \(n\) 是模 \(p\) 意义下的二次剩余,根据 \(n^{p-1}\equiv 1\mod p\) 那么

    因为 \(\exists x^2\equiv n\mod p\) 那么 \(n^{\frac{p-1}2}=x^{p-1}\equiv 1\mod p\)

  • 如果 \(n^{\frac{p-1}2}\equiv 1\mod p\)

    找到 \(p\) 的原根 \(g\)\(n=g^k\mod p\),这里有 \(g^{p-1}\equiv 1\mod p\)

    带入发现 \((p-1)|k\frac {p-1}2 \Leftarrow 2|k\)

找到二次剩余的方法是 Cipolla Algorithm

证明在 skyh 博客,并不复杂,我只简记做法

做法是这样的,首先找到一个 \(a\),满足 \(a^2-n\) 为非二次剩余,这个过程通过随机并检验即可实现。

定义一个类似虚部的东西 \(i\),满足 \(i^2≡a^2-n\mod p\)

\((a+i)^{\frac{p+1}2}\)\(-(a+i)^{\frac{p+1}2}\) 是两个解。

可以证明 \((a+i)^{\frac{p+1}2}\) 不存在虚部,所以实现复数快速幂即可

还有一个欧拉二次互反律:

\[\left(\frac pq\right)=(-1)^{\frac{p-1}2}(-1)^{\frac {q-1}2}\left(\frac qp\right) \]

\(\left(\frac pq\right)\) 取值有 \(-1,0,1\) 三种,分别表示 \(p\) 不是模 \(q\) 意义下二次剩余,\(p\)\(q\) 的倍数和 \(p\) 是模 \(q\) 意义下的二次剩余



我的做法是找到一些是素数的 \(ak+1\) 使用推广过后的判别准则来做,如果 \(n\) 恰好是这个质数的倍数就跳过

出题人比较懒,不把 \(k\) 造大一点然后把所有按上面方法找到的质数的乘积当做 \(n\)

正确性不详,但是已经知道的是如果 \(\exists\) 一个模意义下的二次剩余,开根后是整数的概率约是 \(50\%\)

Code Display
const int N=1e6+10;
char s[N];
int mod[20],cnt,k,val[20];
inline bool is_prime(int x){for(int i=2;i*i<=x;++i) if(x%i==0) return 0; return 1;}
signed main(){
    freopen("math.in","r",stdin); freopen("math.out","w",stdout);
    int T=read();
    while(T--){
        scanf("%s",s+1); k=read();
        cnt=0;
        for(int p=20*k+1;cnt<=9;p+=k) if(is_prime(p)) mod[++cnt]=p,val[cnt]=0;
        int len=strlen(s+1);
        for(int i=1;i<=len;++i){
            rep(j,1,cnt) val[j]=(val[j]*10+s[i]-'0')%mod[j];
        }
        for(int j=1;j<=cnt;++j) if(val[j]){
            if(ksm(val[j],(mod[j]-1)/k,mod[j])!=1){puts("N"); goto Here;}
        }
        puts("Y");
        Here:;
    }
    return 0;
}

求和

如果一个点满足 \(\forall j\in[i-k,i+k],a_j\le a_i\) 那么找到除了 \(i\) 之外 \(a_j\) 最大的 \(j\),这样子的点可以更新答案

不难发现答案的产生也一定在这样子的 \(i\) 里面(使用反证法可以简单得到)

使用支持单点修改,区间查 \(\max\) 的数据结构查合法区间最大值,找到所有可能对答案产生贡献的位置

再使用一个支持单点修改,全局求 \(\max\) 的数据结构来维护可能对答案产生贡献的位置的权值

单点修改的时候找到左右最大的位置并修改这三个位置对答案的贡献

这时候一个严重的问题是如果权值相同维护哪个,最大值位置离被修改的点 \(i\) 远一些就能保证不漏掉 “原来不贡献,现在贡献” 这样子的形式

一种可能的解决方案是强制 \(i\) 满足 \(j<i,a_j\le a_i\) 而右边 \(j>i,a_j<a_i\) 即可,另一种当然可以的方案是维护两个不同比较方式的线段树

感觉上这题和 麻烦的杂货店 有点像,体量很小但是关键一步不容易找到

Code Display
const int N=1e6+10,inf=0x3f3f3f3f3f3f3f3f;
int n,a[N],Q,k,typ;
struct ZKW1{
    int lim,pos[N<<2];
    inline int argmax(int x,int y){return a[x]==a[y]?max(x,y):(a[x]>a[y]?x:y);}
    inline void Modify(int x,int v){
        x=(x+lim)>>1;
        while(x){
            pos[x]=argmax(pos[x<<1],pos[x<<1|1]);
            x>>=1;
        }
        return ;
    }
    inline int query(int l,int r){
        if(r<l) return 0;
        int ans=0;
        l+=lim-1; r+=lim+1;
        while(l!=r-1){
            if(!(l&1)) ans=argmax(ans,pos[l^1]);
            if(r&1) ans=argmax(ans,pos[r^1]);
            l>>=1; r>>=1;
        }
        return ans;
    }
    inline void build(){
        lim=1;
        while(lim<n+1) lim<<=1;
        for(int i=0;i<=n+1;++i) pos[i+lim]=i;
        for(int i=lim-1;i>=1;--i) pos[i]=argmax(pos[i<<1],pos[i<<1|1]);
        return ;
    }
}Mx;
struct ZKW2{
    int Mx[N<<2],lim;
    inline void build(){
        lim=1; while(lim<n) lim<<=1;
        return ;
    }
    inline void Modify(int x,int v){
        if(Mx[x+lim]==v) return ;
        Mx[x+lim]=v;
        for(int pos=(x+lim)>>1;pos;pos>>=1) Mx[pos]=max(Mx[pos<<1],Mx[pos<<1|1]);
        return ;
    }
    inline int top(){return Mx[1];}
}Sum;
inline pair<int,int> askpos(int i){
    int lef=Mx.query(max(1ll,i-k),i-1);
    int rig=Mx.query(i+1,min(i+k,n));
    return make_pair(lef,rig);
}
inline bool push_val(int i){
    pair<int,int> pos=askpos(i);
    if(a[pos.fir]<=a[i]&&a[pos.sec]<a[i]){
        Sum.Modify(i,a[i]+max(a[pos.fir],a[pos.sec]));
        return 1;
    }
    return 0;
}
signed main(){
    freopen("sum.in","r",stdin); freopen("sum.out","w",stdout);
    n=read(); k=read(); Q=read(); typ=read();
    rep(i,1,n) a[i]=read(); a[0]=-1; a[n+1]=inf;
    Mx.build(); Sum.build();
    for(int i=1;i<=n;++i) push_val(i);
    int lans;   
    print(lans=Sum.top());
    while(Q--){
        int x=read()^(typ*lans),y=read()^(typ*lans);
        Mx.Modify(x,a[x]=y);
        pair<int,int> pos=askpos(x);
        if(!push_val(x)){
            Sum.Modify(x,0);
            if(pos.fir) push_val(pos.fir);
            if(pos.sec) push_val(pos.sec);
        }
        print(lans=Sum.top());
    }
    return 0;
}
posted @ 2022-03-13 19:50  yspm  阅读(76)  评论(0编辑  收藏  举报