题目链接:https://nanti.jisuanke.com/t/30996
题目大意:每个房间有一定的灯,每个月买m个灯泡,每次选择从左往右第一个灯数比手上的灯泡数少的房间,将这个房间的所有灯泡换掉。然后q次询问,每次询问第k个月结束以后已经换好了几个房间和手上还有几个灯泡。
我的解法是用一个线段树维护区间最小值来用logn的复杂度找从左往右第一个灯数比手上的灯泡数少的房间,并更新这个房间的数据,因为最多询问到第1e5个月,所以先预处理好每个月结束时的数据即可。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 100005;
struct
{
int num,keep;
}z[maxn];
int cnt,num,cntt;
int n,m;
int tmp[maxn];
int minn[maxn * 4];
void build(int l, int r, int rt)
{
if (l == r)
{
minn[rt] = tmp[l];
return;
}
int mid = (l + r) >> 1;
build(l, mid, rt << 1);
build(mid + 1, r, (rt << 1) + 1);
minn[rt] = min(minn[rt << 1], minn[(rt << 1) + 1]);
}
void update(int l, int r, int rt, int p) //单点更新
{
if (p == l && r == p)
{
minn[rt] = 0x3f3f3f3f;
return;
}
int mid = (l + r) >> 1;
if (p <= mid)
update(l, mid, rt << 1, p);
else
update(mid + 1, r, (rt << 1) + 1, p);
minn[rt] = min(minn[rt << 1], minn[(rt << 1) + 1]);
}
int query(int l, int r, int rt, int l1, int r1, int v) //寻找从左到右第一个灯泡数
少于手上灯泡数的房间,若没有则输出-1
{
if (l == r)
{
if (minn[rt] <= v)
return l;
return -1;
}
if (minn[rt] <= v)
{
int mid = (l + r) >> 1;
int tmp = query(l, mid, rt << 1, l1, r1, v);
if (tmp != -1)
return tmp;
tmp = query(mid + 1, r, (rt << 1) + 1, l1, r1, v);
return tmp;
}
return -1;
}
int main()
{
// freopen("in.txt", "r", stdin);
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; ++i)
scanf("%d", &tmp[i]);
build(1, n, 1); //建树
cnt = 1;
cntt = m;
while (1)
{
int p = query(1, n, 1, 1, n, cntt);
if (p == -1)
{
z[cnt].num = num;
z[cnt++].keep = cntt;
cntt += m;
if (cnt >= maxn || num >= n)
break;
continue;
}
++num; //记录已经搞定的房间数
cntt -= tmp[p]; //手上还有多少个灯泡
update(1, n, 1, p);
}
for (int i = cnt; i < maxn; ++i) //后面多余的月份都与最后一个月相同
z[i] = z[i - 1];
int t;
scanf("%d", &t);
while (t--)
{
int nn;
scanf("%d", &nn);
printf("%d %d\n", z[nn].num, z[nn].keep);
}
return 0;
}