P2827 [NOIP2016 提高组] 蚯蚓
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;
}