完善了下细节.
题意:
- 一个大水库由 n 个体积为 v i vi vi 升的水箱构成,每个水箱都有一个水口,开启后每秒加一升水。如果当前水箱 i 满了,多余的水会超自然地留到 i + 1 的水箱中,直到最后留到大海。
- q 次询问,每次给一个时间 t j tj tj ,问在给定时间内填满 n 个水箱最少要开启的水口数为多少,如果不能填满输出 -1.
思路:
- 显然第一步贪心,如果要开 x 个水口就开在 [1,x] ,在前面开,水溢出后可以补给后面水箱,填满的时间更短。
- 第二步比较容易走偏。其实二分都会想到,但不知道怎么下手。这里应该先推导一个公式:
- 假设已知水库总量 S,给定限定时间为 T;在这个时间限制内,要开的最少水口数其实已经确定了!,开启水口数 c n t > = S / T cnt >= S/T cnt>=S/T (上取整),满足最小所以取等于;
- cnt 确定了,之前贪心也知道是开在前 cnt 个,剩下需要确定的就是 填好前 i 个水箱的最小时间。
- 所以第三步,设置 dp 状态,转移,加上二分优化,这题就解决了。
1900的含金量
剩下看注释
C o d e : Code: Code:
#include<bits/stdc++.h>
#include<unordered_map>
#include<unordered_set>
#define mem(a,b) memset(a,b,sizeof a)
#define cinios (ios::sync_with_stdio(false),cin.tie(0),cout.tie(0))
#define forr(a,b,c) for(int a=b;a<=c;a++)
#define all(a) a.begin(),a.end()
#define sz(a) (int)a.size()
#define oper(a) operator<(const a& ee)const
#define endl '\n'
#define ul (u << 1)
#define ur (u << 1 | 1)
using namespace std;
typedef long long ll;
typedef pair<int, int> PII;
const int N = 2e5 + 10, M = 2e5 + 10, mod = 1e8;
int INF = 0x3f3f3f3f; ll LNF = 0x3f3f3f3f3f3f3f3f;
int n, m, k;
//很明显的贪心,水口尽可能在前开启
int v[N], dp[N];//开启前 i 个水口,同时填满前 i 个水箱的最早时间
ll sum[N];//水量前缀和
void work() {
dp[1] = v[1], sum[1] = v[1];
for (int i = 2; i <= n; i++) {
sum[i] = sum[i - 1] + v[i];
//dp[i] 最早的时间也至少是 dp[i-1],前面填好了才能覆盖后面
int l = dp[i - 1], r = 1e9;
while (l < r)
{
int mid = l + r >> 1;
//mid是时间,i 是水口数
if (1ll * mid * i < sum[i])l = mid + 1;
else r = mid;
}
dp[i] = l;
}
}
void solve() {
cin >> n;
forr(i, 1, n)cin >> v[i];
work();
//明明我们要求的是限定时间内灌满整个水库的最少水口数,为什么dp维护的是这个呢?
//因为已知水库总量 S,给定限定时间 T;
//开启水口数 cnt >= S/T (上取整),满足最小所以取等于;
//
// 意为:满足这个开启数后,水库一定能填满,但时间不知,
// 因为单个 vi 的限制。
//
//知道要开启的水口数后,我们可以直接代入到 dp 中找到最早时间,比较即可
cin >> m;
while (m--)
{
int tim; cin >> tim;
ll g = (sum[n] + tim - 1) / tim;
if (g <= n && dp[g] <= tim)cout << g << endl;
else cout << -1 << endl;
}
}
int main() {
cinios;
int T = 1;
for (int t = 1; t <= T; t++)
solve();
return 0;
}
/*
*/