[HDU6757]Hunting Monsters

220 篇文章 2 订阅
89 篇文章 0 订阅

题目

传送门 to HDU

题意概要
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 aibi 。显然钱的总量在任意时刻都必须是自然数。

请问最开始至少有多少钱时,能够完成至少 k k k 个项目。对于 k = 1 , 2 , 3 , … , n k=1,2,3,\dots,n k=1,2,3,,n,都请求出结果。

数据范围与提示
数据组数 T ⩽ 600 T\leqslant 600 T600 满足 n ⩽ 3 × 1 0 5 n\leqslant 3\times 10^5 n3×105 ∑ n ⩽ 2.5 × 1 0 6 \sum n\leqslant 2.5\times 10^6 n2.5×106

你花钱大手大脚的,保证 1 ⩽ a i , b i ⩽ 1 0 9 1\leqslant a_i,b_i\leqslant 10^9 1ai,bi109

思路

基本思路

如果所有项目都必须完成,那么完成任务的顺序应当是:

  • 先完成 a i ⩽ b i a_i\leqslant b_i aibi 的,按照 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 aibi 的前缀,问题转化为,最开始有 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{fi1(j),fi1(j1)+aibi}
后者转移条件是 f i − 1 ( j − 1 ) + a i ⩽ w f_{i-1}(j{-}1){+}a_i\leqslant w fi1(j1)+aiw,这就是之前我说的凸包类型的贪心。

正确做法

考虑推广 暴力做法。直接求 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,fi1(j1)+aibi}
注意这里以 i i i 为首个项目,故转移顺序是 b i b_i bi 从小到大。而且上式是 i i i 被选的情况,其不被选也是有可能的。

它肯定也类似于凸包,类似于闵可夫斯基和。但含有 max ⁡ \max max,我们只要把它讨论掉,就能得到结果了。不难发现那等价于 f i − 1 ( j ) f_{i-1}(j) fi1(j) b i b_i bi 的大小比较。然后注意到 f i − 1 ( j ) f_{i-1}(j) fi1(j) 是单增的(根据其意义是显然的)。

先找到最大 r r r 使 f i − 1 ( r ) ⩽ b i < a i f_{i-1}(r)\leqslant b_i<a_i fi1(r)bi<ai,那么 f i ( x )    ( x ⩽ r ) f_i(x)\;(x\leqslant r) fi(x)(xr) 都是直接从 f i − 1 ( x ) f_{i-1}(x) fi1(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) fi1(x)(rx) 向后转移。

注意 f i − 1 ( r ) f_{i-1}(r) fi1(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) fi1(r)=bifi1(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 aibi 的项目的效果。当最初的钱的数量达到某个值时,就可以多选几个,那就会使得 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(costix<costi+1)

我们现在想知道,由于 a > b a>b a>b 的项目选 i i i 个,需要 f n ( i ) f_n(i) fn(i) 的 “启动资金”,那么最初需要多少钱,能够在经过 a ⩽ b a\leqslant b ab 的加持后,满足这一条件呢?也就是求最小的 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} costix<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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值