单调队列:从滑动窗口到NOIp2016-蚯蚓

* 先附上网址

https://www.luogu.com.cn/problem/P1886

一道经典题目了。之前就做过(2019年山东省夏令营,2019-07-20 09:14,看来是在礼堂听课时做的
闲话不多说就是单调队列,一种数据结构,维护两个方面:
???还有这功能?直接从ppt里弄出来

为了懒,当然用stl:deque啦(其实是head==tail还是head>tail搞不清楚)

不过用st表也能AC,需要注意卡常数,代码如下:

#include<cstdio>
#include<cmath>
#define min(a,b) (a<b?a:b)
#define max(a,b) (a>b?a:b)
int n,k,a[1000006],st[1000006][22];
inline int read()//不快读的话过得很勉强,快读就比较轻松。另外不要引用isdigit()函数,会变慢
{
	int x=0,f=1;char ch=getchar();
    while (ch>'9'||ch<'0'){if (ch=='-') f=-1;ch=getchar();}
	while (ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
int main(){
    scanf("%d%d",&n,&k);

    for(int i=1;i<=n;++i){
        //scanf("%d",a+i);
        st[i][0]=read();
    }
    for(int j=1,maxx;j<=20;++j){
        maxx=n-(1<<j)+1;
        for(int i=1;i<=maxx;++i)
        st[i][j]=min(st[i][j-1],st[i+(1<<j-1)][j-1]);
    }
    int len=log2(k);//直接用此函数的话,已试验,输入整数时无误差
    for(int i=1;i<=n-k+1;++i)
    printf("%d ",min(st[i][len],st[i+k-(1<<len)][len]));//其实是(i+k-1)-(1<<len)+1
    
    printf("\n");
    
     for(int j=1,maxx;j<=20;++j){
        maxx=n-(1<<j)+1;
        for(int i=1;i<=maxx;++i)
        st[i][j]=max(st[i][j-1],st[i+(1<<j-1)][j-1]);
    }
    for(int i=1;i<=n-k+1;++i)
    printf("%d ",max(st[i][len],st[i+k-(1<<len)][len]));

    return 0;
}

接下来是蚯蚓这道题

https://www.luogu.com.cn/problem/P2827

/**
 * 蛐蛐国里现在共有 n 只蚯蚓( n 为正整数)。
 * 每只蚯蚓拥有长度,我们设第 i 只蚯蚓的长度为 a[i]
 * (i=1,2,...,n)
 * 并保证所有的长度都是非负整数
 * (即:可能存在长度为 0 的蚯蚓)。
*//**
 * 每一秒,神刀手会在所有的蚯蚓中,
 * 准确地找到最长的那一只(如有多个则任选一个)
 * 将其切成两半。
 * 神刀手切开蚯蚓的位置由常数 p
 * (是满足 0 < p < 1 的有理数)
 * 决定,设这只蚯蚓长度为 x,
 * 神刀手会将其切成两只长度分别为
 * ⌊px⌋ 和 x - ⌊px⌋ 的蚯蚓。
 * 特殊地,如果这两个数的其中一个等于 0,
 * 则这个长度为 0 的蚯蚓也会被保留。
 * 此外,除了刚刚产生的两只新蚯蚓,
 * 其余蚯蚓的长度都会增加 q(是一个非负整常数)。
*/

附上ckw大佬的分析(也是2019年夏令营的)

那么思想的话已经很明显了。正解也很多,文章最后贴一个正解做法(我们教练写的)。这里给一个骗分的做法:

NOIp部分分给的非常足!且数据一般不会出现极限数据,必须卡常之类的

这就给我们快速拿 部分分 提供了可乘之机

我们快速写一个不是正解但是一定对的代码然后疯狂卡常,如下:
考虑到每次取最大蚯蚓,且不断插入/删除,我们用一个优先队列来维护
其他的就是照着题面模拟了。需要注意的是每次插入新的蚯蚓时要减去time×q以“拉平”,
换言之,其他蚯蚓都少加了time×q,切的这条蚯蚓切的时候是真实的长度,切后要变回“虚假”的长度
其他的按照体面输入输出就完了。开不开O2优化都是85分,优化作用也有限。

#include<cstdio>
#include<queue>
using namespace std;
int _X,_F,CH;
inline int read(){//read
    _X=0;_F=1;CH=getchar();
    while(CH>57||CH<48){if(CH=='-')_F=-1;CH=getchar();}
    while(CH>=48&&CH<=57){_X=_X*10+CH-48;CH=getchar();}
    return _X*_F;
}

priority_queue<int/*,vector<int>,greater<int> */>pq;
int n,m,q,u,v,t/*,a[100005]*/;//含义见题目
int longest,shorter;//变量名起的长一点便于理解
int main(){
    n=read();m=read();q=read();u=read();v=read();t=read();
    for(int i=1;i<=n;++i)
    {
        //a[i]=read();
        pq.push(read());
    }
    for(int time=1;time<=m;++time)
    {
        longest=pq.top();pq.pop();
        longest+=(time-1)*q;//本轮他并没有变长
        if(time%t==0)printf("%d ",longest);
        shorter=longest*(long long)u/v;//这个long long常数很大,但是必须
        pq.push(shorter-time*q);pq.push(longest-shorter-time*q);
//写这篇文章时我想到了把time*q存起来,但是该拿的85分到手了,剩下几个点真的没什么用
    }
    putchar('\n');
    int addlen=m*q;//<2^31
    for(int i=1;i<=n+m;++i)
    {
        if(i%t){pq.pop();continue;}
        longest=pq.top();pq.pop();
        printf("%d ",longest+addlen);
    }
    
    return 0;
}

这是一份正解,三个单调队列,含义如ckw所述。题解满大街都是,贴个我们教练的:

 # include<bits/stdc++.h>
 #define rep(i,n) for(int i=1;i<=n;++i) 

  using namespace std;
  inline int read()
  {
    int x=0,f=1;
    char ch;ch=getchar();
    while(ch<48 ||ch>57){if(ch=='-') f=-1;ch=getchar(); }
    while(ch>=48&&ch<=57) {x=x*10+ch-48;ch=getchar();}
    return x*f;
  }
  const int  N=1e5+5;
  const int  M=1e7;
  int a[3][M],cut[M],h[3],t[3];//a[0] 原数列  a[1]  砍断后长的段  a[2]砍断后短的段
  // cut [] 记录被砍断的蚯蚓  h[0] h[1] h[2] 代表 a[0] a[1] a[2]头指针 t[]分别是尾指针 
  int n,m,q,u,v,T,inf;
   bool cmp(int &a,int &b) {return a>b;}
 int main()
 {  //freopen("in.txt","r",stdin);
   cin>>n>>m>>q>>u>>v>>T;
    
   for(int i=0;i<=2;++i) rep(j,M)a[i][j]=-1e9;
   inf=-1e9;
 
  h[0]=h[1]=h[2]=1; cut[0]=0;
   rep(i,n) a[0][i]=read();
   sort(a[0]+1,a[0]+1+n,cmp);// 首先让a[0] 从大到小 有序 

   double per=u*1.0/v;
   rep(i,m)// 开始砍蚯蚓 
   {
     int temp,maxn;
      if(a[0][h[0]]>=a[1][h[1]])  maxn=a[0][h[0]],temp=0;
      else maxn=a[1][h[1]],temp=1;
      if(a[2][h[2]]>maxn) maxn=a[2][h[2]],temp=2;
    // 找到a[0]  a[1] a[2] 队首最大的 就是 应该被砍的蚯蚓 
      h[temp]++;//被砍的蚯蚓被弹出队首 
      
      if(i%T==0)cut[++cut[0]]=maxn+(i-1)*q;//被砍的蚯蚓应当加上增长补偿,每一秒都增长q 
      // 接下来把砍断的蚯蚓 按大小存在a[1]  a[2] 队列 
      int small=(maxn+(i-1)*q)*per,big=maxn+(i-1)*q-small;
     if(big<small) swap(big,small);
       a[1][++t[1]]=big-i*q,a[2][++t[2]]=small-i*q;
       
   }
   //按要求输出  事后我想cut数组也许没有必要  直接输出即可 
  rep(i,cut[0]) cout<<cut[i]<<" ";   cout<<endl;
 
  
  int left=(n+m)/T,cnt=0;
  // 后来身负洪荒之力人来了  蚯蚓不在增长  只需要把序列输出即可 
  while(left)
  {
   int temp,maxn=inf;
      if(a[0][h[0]]>=a[1][h[1]])  maxn=a[0][h[0]],temp=0;
      else maxn=a[1][h[1]],temp=1;
      if(a[2][h[2]]>=maxn) maxn=a[2][h[2]],temp=2;   
       
      h[temp]++;
      if(++cnt%T==0)cout<<maxn+m*q<<" ",left--;

  }
  
   
   
 return 0;
 }

还有一位同学因常数过大被卡所以一定快读

文末总结:
一些看似高级的题目其实可能有简单(吗?)的做法,尤其是单调队列,好几次出现了
所以还是那句话,基础算法灵活掌握。
另外就是ckw教的经验吧,先考虑部分分的限制条件,不行就先自己加一个,在这种受限的背景下题目还会含有什么限制条件,
再推广到更高分看是不是还成立。我觉得部分分就挺香的了

posted @ 2020-08-09 22:42  全球通u1  阅读(145)  评论(0编辑  收藏  举报