P2827 [NOIP2016 提高组] 蚯蚓

P2827 [NOIP2016 提高组] 蚯蚓

P2827|ac133

85 pts

看到题的思路就是这里每次取最大值,可以使用优先队列大根堆维护每次的最大值,还有一个操作就是每次队列中除去最大元素,其他元素都要加上一个\(q\),那么这里就是关键的两个操作就是 最大值 \(+\) 区间加法。区间加法:线段树,但是不行,这里的优先队列没有迭代器,无法返回区间的左右端点。考虑一下其他元素的加 \(q\),实际上是划出来的两个数减 \(q\),这样其实相对大小其实没有改变。每次取出的最大值虽然值不对,但是一定是和原操作一样是最大的。每次取出的值和应该取出的值满足一定的关系,对于\(x\),当第\(i\) 秒被分出来,将\(x - i * q\)加入优先队列中,下次第\(j\)秒时取到这个数(\(cnt = x - i * q\))时,这时将\(cnt + (j - 1) * q\),这样就是等价于在\(i \sim j\)这段时间内所一共需要加的\(q\)的总数。
具体看代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define re register
const int N = 1e5 + 5;
const int M = 8e6;
ll a[N];
//
priority_queue<ll> qx;
ll ansv[M];
ll ans[M];
int ttv = 0,tts = 0;

inline int read()
{
	re int x=0,f=1;re char c=getchar();
	while(c<'0'||c>'9') {if(c=='-') f=-1;c=getchar();}
	while (c>='0'&&c<='9') x=(x<<3)+(x<<1)+(c^48),c=getchar();
	return x*f;
}
int main()
{
    ll n,m,q,u,v,t;
    cin >> n >> m >> q >> u >> v >> t;
    for(int i= 1;i <= n;i++)
    {
        a[i] = read();
        qx.push(a[i]);
    }
    double _ = (double)u / v;
    for(int i = 1;i <= m;i++)
    {
        ll vl = (ll)qx.top();
        qx.pop();
        vl += (ll)(i - 1) * q;
        ansv[++ttv] = vl;
        ll px = (ll)vl * _; 
        ll x_px = vl - px;
        px -= (ll)i * q;
        x_px -= (ll)i * q;
        qx.push(px);
        qx.push(x_px);
    }
    while(qx.size())
    {
        ll x = qx.top();
        x += m * q;
        ans[++tts] = x;
        qx.pop();
    }
    for(int i =t;i <= ttv;i += t)
    {
       printf("%lld ",ansv[i]);
    }
    puts("");
    for(int i =t;i <= tts;i+=t)
    {
       printf("%lld ",ans[i]);
    }
    return 0;
}

100 pts

这里如果使用优先队列的话,复杂度不行。所以想办法优化,考虑上面的关键字:优先队列 \(+\) 区间加法优化,区间加法优化已经是最优的情况了,所以在这里需要将优先队列的\(log\)优化成\(O(n)\).
那么就是需要解决选择每次取出的最大值的问题。现在考虑维护三个队列:原来的数组:\(q[0]\)\(px\)的数组\(q[1]\),\(x-px\)的数组\(q[2]\).这里其实利用了一个单调的性质,这三个队列都是单调递减的序列。

  • 初始化\(q[0]\)队列,先使队列单调递减。
  • 然后先从\(q[0]\)队列将最大元素\(maxn\_value\)取出,作为切分对象,这里的\(maxn\_value\)也是剩下的数中最大的,然后计算第一次的\(px,x-px\),插入到\(q[1],q[2]\)中,等到下一次需要计算最大值时,只可能在 \(q[0],q[1],q[2]\)的队头,因为:现在的\(q[0]\)的队头是第一次操作的最小值,如果没有切分操作那么现在的队头就是最大值,而现在\(maxn\_value\)被划分成两个数,那么这两个数有可能是最大值的候选项。以此类推。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
queue<int> qx[4];
const int N = 1e5 + 5;
const int M = 8e6;
ll a[N];
ll ansv[M];
ll ans[M];
int ttv = 0,tts = 0;
ll solve()
{
   //返回最大值
   ll ans = -9223372036854775808;
   //找到需要pop的队列
   int maxn = 0;
   //循环三个队列,在队头找到最大值
   for(int i= 0;i < 3;i++){
       //保证队列有数
       if(qx[i].size())
       {
           //
           ll _ = qx[i].front();
           if(_ > ans)
           {
               ans = _;
               maxn = i;
           }
       }
   }
   qx[maxn].pop();
   return ans; 
}
bool cmp(int a,int b)
{
    return a > b;
}
int main()
{
    ll n,m,q,u,v,t;
    cin >> n >> m >> q >> u >> v >> t;
    for(int i =1;i <= n;i++)
    {
        scanf("%lld",&a[i]);
    }
    sort(a + 1,a + n + 1,cmp);
    for(int i =1; i<= n;i++)
      qx[0].push(a[i]);
    double x = (double)u / v;
    for(ll i= 1;i <= m;i++)
    {
       ll vl = solve();
       vl += (ll)(i - 1) * q;
       ansv[++ttv] = vl;
       ll px = (ll) vl * x;
       ll x_px = vl - px;
       px -= (ll)i * q;
       x_px -= (ll)i * q;       
       qx[1].push(px);
       qx[2].push(x_px);
    }   
    for(int i =0; i< 3;i++)
    {
        while(qx[i].size())
        {
            ll _ = qx[i].front();
            _ += (ll)m * q;
            ans[++tts] = _;
            qx[i].pop();
        }
    }
    sort(ans + 1,ans + tts + 1,cmp);
    for(int i =t;i <= ttv;i += t)
    {
       printf("%lld ",ansv[i]);
    }
    puts("");
    for(int i =t;i <= tts;i+=t)
    {
       printf("%lld ",ans[i]);
    }
    return 0;
}
posted @ 2021-02-21 20:58  strategist_614  阅读(56)  评论(0编辑  收藏  举报