• NOIP模拟赛(by hzwer) T2 小奇的序列


    【题目背景】

      小奇总是在数学课上思考奇怪的问题。

    【问题描述】

      给定一个长度为 n 的数列,以及 m 次询问,每次给出三个数 l,r 和 P, 询问 (a[l'] + a[l'+1] + ... + a[r']) mod P 的最小值。 其中 l <= l' <= r' <= r。

      即模意义下的区间子串和最小值。

    【输入格式】

      第一行包含两个正整数 n 和 m,表示数列的长度和询问的个数。

      第二行为 n 个整数,为 a[1]..a[n]。

      接下来 m 行,每行三个数 l,r 和 P,代表一次询问。

    【输出格式】

      对于每次询问,输出一行一个整数表示要求的结果

    【样例输入】

      4 2

      8 15 9 9

      1 3 10

      1 4 17

    【样例输出】

      2

      1

    【数据范围】

      对于 20%的数据 n<=100,m<=100,p<=200

      对于 40%的数据 n<=200,m<=1000,p<=500

      对于 70%的数据 n<=100000,m<=10000,p<=200

      对于 100%的数据 n<=500000,m<=10000,p<=500,1<=a[i]<=10^9

    【解析】

      这道题第一眼看过去好像没什么思路。从部分分入手吧。

      20分:直接暴力枚举每一个区间里的点,然后求最小值即可。

      40分:可以使用前缀和优化,然后就可以很快地求一个区间里的和。

      70分:假如数据是绝对随机,那么如果一个区间的长度大于p,该区间中一定可以组合出所有区间和%p的余数。其中一定会有余数为0的情况,输出0即可。经过实测,这种方法可以拿90分。

      100分:将前缀和改为区间前缀和,那么设i为当前枚举到的点,那么区间[ l , i ]的余数sum[i]=(sum[i-1]+a[i])%p。记vis[i]表示余数为i的情况在前面是否出现过。对于每一个sum[i],我们都枚举比他小的余数(从sum[i]到0,用j表示),如果vis[j]=1,表示有这种情况。假设余数为j的位置为p,那么由模运算的性质可得:区间[ p , i ]的余数为sum[i]-j。(为什么大于sum[i]的不需要?因为这不可能是更优的解)然后,用sum[i]-j去更新答案。由于余数是从大到小枚举的,且j越大sum[i]-j越小,即该区间余数越小,如果当前能够被更新,后面的就不会再对答案造成影响了,直接break即可。最后把vis[sum[i]]设为1。注意,如果当前答案已经为0了,就直接输出(不会再更优了)。另外,不要每次都去清空前缀和数组,只需要把sum[l-1]设为0即可。

    【备注】

      这道题的正解其实是平衡树,但我并不会这个东西......

    【代码】

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #define N 500002
    using namespace std;
    int n,m,a[N],l,r,p,i,j,k;
    long long sum[N];
    bool e[500002];
    long long read()
    {
        char c=getchar();
        long long w=0;
        while(c<'0'||c>'9') c=getchar();
        while(c<='9'&&c>='0'){
            w=w*10+c-'0';
            c=getchar();
        }
        return w;
    }
    long long min(long long a,long long b)
    {
        if(a<b) return a;
        return b;
    }
    int main()
    {
        freopen("seq.in","r",stdin);
        freopen("seq.out","w",stdout);
        n=read();
        m=read();
        for(i=1;i<=n;i++) a[i]=read();
        while(m--){
            l=read();
            r=read();
            p=read();
            if(r-l+1>p){
                cout<<"0"<<endl;
                continue;
            }
            for(i=1;i<=p;i++) e[i]=0;
            e[0]=1;
            sum[l-1]=0;
            long long ans=1<<30;
            for(i=l;i<=r;i++){
                sum[i]=(sum[i-1]+a[i])%p;
                for(j=sum[i];j>=0;j--){
                    if(e[j]){
                        ans=min(ans,sum[i]-j);
                        break;
                    }
                }
                if(ans==0) break;
                e[sum[i]]=1;
            }
            printf("%lld
    ",ans);
        }
        fclose(stdin);
        fclose(stdout);
        return 0;
    }
  • 相关阅读:
    最短路径之spfa
    最短路径之Bellman-Ford——解决负权边
    最短路径之Floyd-Warshall算法
    图上最短路径问题
    它们其实都是图(二分图)
    记忆化结果再利用 进一步探讨递推关系
    leetcode 376. 摆动序列 java
    leetcode 368. 最大整除子集 java
    leetcode 96. 不同的二叉搜索树 java
    leetcode 454. 四数相加 II java
  • 原文地址:https://www.cnblogs.com/LSlzf/p/10393135.html
Copyright © 2020-2023  润新知