2018年多校第三场第一题 A. Ascending Rating hdu6319

比赛地址:http://acm.hdu.edu.cn/contests/contest_show.php?cid=804

题目编号:第一题 A. Ascending Rating  hdu6319

题意:

给定一个序列 a[1..n],对于每个长度为 m 的连续子区间, 求出区间 a 的最大值以及从左往右扫描该区间时 a 的最大值的 变化次数。

题解:按照 r 从 m 到 n 的顺序很难解决这个问题。 考虑按照 r 从 n 到 m 的顺序倒着求出每个区间的答案。 按照滑窗最大值的经典方法维护 a 的单调队列,那么队列 中的元素个数就是最大值的变化次数。 时间复杂度 O(n)。

我的理解:由于从前向后的顺序来做的话,当窗口向后滑动一格时,当前L的值若比L-1的值小的话,那么可能会出现之前没有在队列里的值需要重新进队的情况,所以很难做到O(n),ljj虽然有想法,但是直观上来看,从后向前的顺序更简单一点。因为每次向前滑动一格,队头如果是R,那么去掉队头,如果不是R那么R值对后面也没有用且R值本身并没有在队列中,所以是O(1),对于L,将L值插入单调队列并设置为新的队尾即可,新队尾之前的部分在之后不可能再用到,所以每个值最多只可能在队列中被新增和删除一次,所以平均每个窗口滑动对于L值得操作也是O(1)的。队列就开个足够大数组往后挪就好了,懒得写循环队列了。然后取模本身是很慢的操作,尽量少取模吧(删了几个%就ac了)。。。

自己的代码还是参考了下题解的:

#include<cstdio>
#include<cstdlib>
#include<iostream>
#include<cmath>
using namespace std; 
int T;
long long n,m,k,p,q,r,MOD;
long long a[30000000];
long long q1[30000000];
long long ans1,ans2;
int main()
{
    scanf("%d",&T);
    while(T--)
    {
        scanf("%lld%lld%lld%lld%lld%lld%lld",&n,&m,&k,&p,&q,&r,&MOD);
        for(int i=1;i<=k;i++)
            scanf("%lld",&a[i]);
        for(int i=k+1;i<=n;i++)
        {
            a[i]=(((p*a[i-1])+(q*i))+r)%MOD;
        }
        int top=0;//用来记录当前单调队列队顶的位置,因为数组大于总共的ai的个数,所以不需要每次把新队列整理,而是动态的用这个空间,记录当前队列的队顶队尾即可。 
        int rec=0;//队尾的位置 
        for(int i=n;i>=n-m+1;i--)
        {
            while(rec>=top&&a[i]>=a[q1[rec]])rec--;
            rec++;
            q1[rec]=i;
        }
        ans1+=a[q1[top]]^(n-m+1);
        ans2+=(rec-top+1)^(n-m+1);
        int i=n-m;
        for(int l=n-m;l>=1;l--)
        {
            int r=l+m-1;
            if(r+1==q1[top])//当新区间不包括当前队顶时,则更新队列,去掉队顶,队尾前移。 
            {
                top++;
            }
            while(rec>=top&&a[l]>=a[q1[rec]])rec--;
            rec++;
            q1[rec]=i;
            ans1+=a[q1[top]]^i;
            ans2+=(rec-top+1)^i;
            i--;
        }
        printf("%lld %lld\n",ans1,ans2);
        ans1=ans2=0;
        q1[0]=0;
    }
}

猜你喜欢

转载自www.cnblogs.com/witRY/p/9398789.html