JZOJ5431. 序列操作
题目大意
有\(n\)个非负整数, 接下来会进行m次操作, 第i次操作给出一个数\(c[i]\), 要求你选出\(c[i]\)个大于零的数并将它们减去\(1\).
问最多可以进行多少轮操作后无法操作(即没有\(c[i]\)个大于零的数).
\(n<=10^6\)
解题思路
显然最优方案是从大到小排序, 选取最大的\(c_i\)个数然后 关掉 减去\(1\), 但是有一个问题, 对前\(c_i\)个减\(1\)之后, 可能就不满足单调递减性了.. 考场上这个问题困扰了我好久, 最终还是没有A(
如何解决这个问题呢? 实际上我们需要有分析数据特征的思想. 一般来说, 区间修改, 并且要维护单调性, 这是很难实现的(如图1, 绿色的是要修改的部分). 但是我们每一次只减\(1\), 并且修改范围是从\(1\)开始的一个连续的区间, 就可以靠这个搞点大事情.

\[\text{图1:错误的示范}
\]
首先思考, 什么时候单调性会改变呢? 对, 就是在一段相同的区间中, 我对左边的一部分进行了减\(1\), 右边却没有减, 左边那一部分就会跑到右边那一部分的右边去对吧?
那么, 由于这一整段都是一样的, 我何苦左边减了一部分, 再移到右边去呢? 我为什么不从右边选取一段同样长度的序列再直接减去呢? 这样不就解决了吗?

\[\text{图2:正确的做法}
\]
于是就把原问题变成了如下问题: 支持区间减\(1\), 单点查询, 查询最左边的与\(v\)相同的数的下标, 查询最右边的与\(v\)相同的数的下标.
这个东西.. 用线段树维护一些奇怪的东西应该是可以做到\(O(n\log{n})\)的, 但是我比较懒, 所以直接套了一个二分( 于是时间复杂度变成了\(O(n\log^2{n})\), 随便过好吧(
后记
一定要注意分析数据的特点, 选择正确而巧妙的做法来解题.
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 1000010
#define init(a, b) memset(a, b, sizeof(a))
#define fo(i, a, b) for(int i = (a); i <= (b); ++i)
#define fd(i, a, b) for(int i = (a); i >= (b); --i)
using namespace std;
inline int read()
{
int x = 0; char ch = getchar();
while(ch < '0' || ch > '9') ch = getchar();
while(ch >= '0' && ch <= '9') x = (x << 3) + (x << 1) + (ch ^ 48), ch = getchar();
return x;
}
int n, m, W, p2[20] = {1}, a[N];
namespace Seg
{
#define ls t << 1
#define rs ls | 1
#define mid ((l + r) >> 1)
int tag[N << 2], tr[N << 2];
inline void push_up(int t){tr[t] = tr[ls] + tr[rs];}
inline void push_down(int t, int l, int r)
{
if(!tag[t]) return ;
tag[ls] += tag[t], tag[rs] += tag[t];
tr[ls] += (mid - l + 1) * tag[t], tr[rs] += (r - mid) * tag[t];
tag[t] = 0;
}
inline void build(int t, int l, int r)
{
if(l == r) return (void)(tr[t] = a[l], 0);
build(ls, l, mid); build(rs, mid + 1, r);
push_up(t);
}
void update(int t, int l, int r, int fl, int fr)
{
if(fl <= l && r <= fr) return (void)(--tag[t], tr[t] -= (r - l + 1), 0);
push_down(t, l, r);
fl <= mid && (update(ls, l, mid, fl, fr), 1);
fr > mid && (update(rs, mid + 1, r, fl, fr), 1);
push_up(t);
}
int query(int t, int l, int r, int w)
{
if(l == r) return tr[t];
push_down(t, l, r);
return w <= mid ? query(ls, l, mid, w) : query(rs, mid + 1, r, w);
}
inline void update(int l, int r){update(1, 1, n, l, r);}
inline int query(int w){return query(1, 1, n, w);}
}
inline bool cmp(int x, int y){return x > y;}
inline int ask(int dir, int w, int v)
{
fd(i, W, 0)
if(w + dir * p2[i] <= n && w + dir * p2[i] > 0 && Seg::query(w + dir * p2[i]) == v)
w += dir * p2[i];
return w;
}
int main()
{
freopen("sequence.in", "r", stdin);
freopen("sequence.out", "w", stdout);
n = read(), m = read();
for(; p2[W] <= n && W <= 18; ++W) p2[W + 1] = p2[W] << 1;
fo(i, 1, n) a[i] = read();
sort(a + 1, a + n + 1, cmp);
Seg::build(1, 1, n);
fo(i, 1, m)
{
int c = read();
int v = Seg::query(c);
if(v == 0)
{
printf("%d", i - 1);
return 0;
}
int l = ask(-1, c, v), r = ask(1, c, v);
if(l > 1) Seg::update(1, l - 1);
Seg::update(r - c + l, r);
}
printf("%d", m);
return 0;
}
浙公网安备 33010602011771号