题目
题意概要
有
n
n
n 个项目,第
i
i
i 个项目会花费
a
i
a_i
ai 元,得到
b
i
b_i
bi 元的利润。不保证
a
i
⩽
b
i
a_i\leqslant b_i
ai⩽bi 。显然钱的总量在任意时刻都必须是自然数。
请问最开始至少有多少钱时,能够完成至少 k k k 个项目。对于 k = 1 , 2 , 3 , … , n k=1,2,3,\dots,n k=1,2,3,…,n,都请求出结果。
数据范围与提示
数据组数
T
⩽
600
T\leqslant 600
T⩽600 满足
n
⩽
3
×
1
0
5
n\leqslant 3\times 10^5
n⩽3×105 且
∑
n
⩽
2.5
×
1
0
6
\sum n\leqslant 2.5\times 10^6
∑n⩽2.5×106 。
你花钱大手大脚的,保证
1
⩽
a
i
,
b
i
⩽
1
0
9
1\leqslant a_i,b_i\leqslant 10^9
1⩽ai,bi⩽109 。
思路
基本思路
如果所有项目都必须完成,那么完成任务的顺序应当是:
- 先完成 a i ⩽ b i a_i\leqslant b_i ai⩽bi 的,按照 a i a_i ai 不降的顺序。
- 再完成 a i > b i a_i>b_i ai>bi 的,按照 b i b_i bi 不升的顺序。
用 exchange argument \text{exchange argument} exchange argument 易证后者。前者是显然的。
暴力做法
若枚举 a i ⩽ b i a_i\leqslant b_i ai⩽bi 的前缀,问题转化为,最开始有 w w w 元,能选多少 a j > b j a_j>b_j aj>bj 的项目?
考虑
d
p
\tt dp
dp,用
f
i
(
j
)
f_i(j)
fi(j) 表示选
j
j
j 个所花费的最少钱,有转移
f
i
(
j
)
=
min
{
f
i
−
1
(
j
)
,
f
i
−
1
(
j
−
1
)
+
a
i
−
b
i
}
f_i(j)=\min\{f_{i-1}(j),\;f_{i-1}(j{-}1){+}a_i{-}b_i\}
fi(j)=min{fi−1(j),fi−1(j−1)+ai−bi}
后者转移条件是
f
i
−
1
(
j
−
1
)
+
a
i
⩽
w
f_{i-1}(j{-}1){+}a_i\leqslant w
fi−1(j−1)+ai⩽w,这就是之前我说的凸包类型的贪心。
正确做法
考虑推广 暴力做法
。直接求
f
i
(
j
)
f_i(j)
fi(j) 为最少的 “启动代价”,则有转移
f
i
(
j
)
←
max
{
a
i
,
f
i
−
1
(
j
−
1
)
+
a
i
−
b
i
}
f_i(j)\gets\max\{a_i,\;f_{i-1}(j{\rm-}1){\rm+}a_i{\rm-}b_i\}
fi(j)←max{ai,fi−1(j−1)+ai−bi}
注意这里以
i
i
i 为首个项目,故转移顺序是
b
i
b_i
bi 从小到大。而且上式是
i
i
i 被选的情况,其不被选也是有可能的。
它肯定也类似于凸包,类似于闵可夫斯基和。但含有 max \max max,我们只要把它讨论掉,就能得到结果了。不难发现那等价于 f i − 1 ( j ) f_{i-1}(j) fi−1(j) 和 b i b_i bi 的大小比较。然后注意到 f i − 1 ( j ) f_{i-1}(j) fi−1(j) 是单增的(根据其意义是显然的)。
先找到最大 r r r 使 f i − 1 ( r ) ⩽ b i < a i f_{i-1}(r)\leqslant b_i<a_i fi−1(r)⩽bi<ai,那么 f i ( x ) ( x ⩽ r ) f_i(x)\;(x\leqslant r) fi(x)(x⩽r) 都是直接从 f i − 1 ( x ) f_{i-1}(x) fi−1(x) 得来。只需要考虑 f i ( x ) ( r < x ) f_i(x)\;(r<x) fi(x)(r<x) 的更新,也就是 f i − 1 ( x ) ( r ⩽ x ) f_{i-1}(x)\;(r\leqslant x) fi−1(x)(r⩽x) 向后转移。
注意 f i − 1 ( r ) f_{i-1}(r) fi−1(r) 是唯一用 a i a_i ai 转移的,那我们可以建 虚点 f i − 1 ′ ( r ) = b i ⩾ f i − 1 ( r ) f'_{i-1}(r)=b_i\geqslant f_{i-1}(r) fi−1′(r)=bi⩾fi−1(r),统一格式。此时是经典的闵可夫斯基和。也就是说,虚点及其后面的实点,始终构成一个凸包。
最重要的是 b i b_i bi 是单调不降的。那么 r r r 只会增大。增大后,将某个实点改成虚点,由于虚点的函数值(即 y y y 坐标)比原来的实点的函数值更大,所以仍然构成凸包。那么就一直进行闵科夫斯基和就行了。
这样,我们可以 O ( n log n ) \mathcal O(n\log n) O(nlogn) 的求出 f n ( i ) f_n(i) fn(i) 数组。
合并答案
最终怎么得到答案呢?只需要搞清楚
a
i
⩽
b
i
a_i\leqslant b_i
ai⩽bi 的项目的效果。当最初的钱的数量达到某个值时,就可以多选几个,那就会使得
e
a
r
n
earn
earn 减
c
o
s
t
cost
cost 变大。所以就是一个分段函数
g
(
x
)
=
x
+
e
a
r
n
i
(
c
o
s
t
i
⩽
x
<
c
o
s
t
i
+
1
)
g(x)=x+earn_i\quad(cost_i\leqslant x<cost_{i+1})
g(x)=x+earni(costi⩽x<costi+1)
我们现在想知道,由于 a > b a>b a>b 的项目选 i i i 个,需要 f n ( i ) f_n(i) fn(i) 的 “启动资金”,那么最初需要多少钱,能够在经过 a ⩽ b a\leqslant b a⩽b 的加持后,满足这一条件呢?也就是求最小的 x x x 使得 g ( x ) ⩾ f n ( i ) g(x)\geqslant f_n(i) g(x)⩾fn(i) 嘛。
显然 g ( x ) g(x) g(x) 也是单调的。并且我们知道 n n n 个特殊点的函数值, g ( c o s t i ) = c o s t i + e a r n i g(cost_i)=cost_i+earn_i g(costi)=costi+earni,那就可以先二分找到这个范围,然后看看 c o s t i ⩽ x < c o s t i + 1 cost_i\leqslant x<cost_{i+1} costi⩽x<costi+1 是否有解就行了。
当然可以由二分改为 p o i n t e r \rm pointer pointer 的方法,反正不会是瓶颈。总时间复杂度 O ( n log n ) \mathcal O(n\log n) O(nlogn) 。
代码
由于数据有问题,必须使用 s c a n f \rm scanf scanf 进行读入。
#include <cstring>
#include <cstdio>
#include <iostream>
#include <vector>
#include <algorithm>
#include <queue>
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/priority_queue.hpp>
using namespace __gnu_pbds;
using namespace std;
typedef long long int_;
# define rep(i,a,b) for(int i=(a); i<=(b); ++i)
# define drep(i,a,b) for(int i=(a); i>=(b); --i)
inline int readint(){
int x; scanf("%d",&x); return x;
}
inline void writeint(int x){
if(x < 0) putchar('-'), x = -x;
if(x > 9) writeint(x/10);
putchar((x-x/10*10)^48);
}
const int MaxN = 300005;
const int infty = (1<<30)-1;
struct Enemy{
int a, b;
Enemy(){ }
Enemy(int A,int B){
a = A, b = B;
}
bool operator < (const Enemy &t) const {
return b < t.b;
}
};
Enemy enemy[MaxN];
int_ need[MaxN]; // may cost a lot
// priority_queue< int,vector<int>,greater<int> > pq;
__gnu_pbds::priority_queue< int,greater<int> > pq;
void fightEnemy(int n){
int pos = 0; // fixed length
int_ cur = 0; // (virtual) need[pos]
pq.push(infty); // boundary
for(int i=1; i<=n; ++i){
while(pq.top()+cur <= enemy[i].b){
cur += pq.top(); pq.pop();
need[++ pos] = cur;
}
if(pq.top() != infty){
int x = pq.top(); pq.pop();
pq.push(x-(enemy[i].b-cur));
}
cur = enemy[i].b; // new value
pq.push(enemy[i].a-enemy[i].b); // minkowski
}
for(; !pq.empty(); pq.pop())
need[++ pos] = (cur += pq.top());
}
struct Comrade{
int a, b;
Comrade(){ }
Comrade(int A,int B){
a = A, b = B;
}
bool operator < (const Comrade &t) const {
return a < t.a;
}
};
Comrade comrade[MaxN];
int cost[MaxN]; int_ got[MaxN];
void meetComrade(int n){
for(int i=1; i<=n; ++i)
if(comrade[i].a > got[i-1]){
cost[i] = cost[i-1]+comrade[i].a-got[i-1];
got[i] = comrade[i].b;
}
else{
cost[i] = cost[i-1];
got[i] = got[i-1]+comrade[i].b-comrade[i].a;
}
}
const int Mod = 1e9+7;
int ddg[MaxN], ans[MaxN];
int main(){
for(int T=readint(); T; --T){
int n = 0, m = 0;
int tot = readint();
rep(i,1,tot) ddg[i] = readint();
for(int i=1,a,b; i<=tot; ++i){
a = ddg[i], b = readint();
if(a > b) enemy[++ m] = Enemy(a,b);
else comrade[++ n] = Comrade(a,b);
}
sort(comrade+1,comrade+n+1);
sort(enemy+1,enemy+m+1);
meetComrade(n); fightEnemy(m);
int rnk = 0, pa = 1, pb = 1;
for(; rnk!=n&&got[rnk+1]<need[pb]; ++rnk);
while(pa <= n || pb <= m){
int_ vb = cost[rnk]+need[pb]-got[rnk];
if(rnk != n && vb > cost[rnk+1])
vb = cost[rnk+1]; // got >= vb
int va = cost[pa]; // not check range
if(pa > n || vb < va){
ans[pa+pb-1] = vb%Mod, ++ pb;
for(; got[rnk+1]<need[pb]; ++rnk)
if(rnk == n) break;
}
else ans[pa+pb-1] = va, ++ pa;
}
int xyx = 0; // output
rep(i,1,tot) xyx = (xyx+
int_(i)*ans[i]%Mod)%Mod;
writeint(xyx), putchar('\n');
}
return 0;
}