題目鏈接: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;
}