众所周知,\(XM\) 是一个爱吃面筋的孩子。
lcez的面筋生产部有\(n\)栋楼排成一列,每栋楼有\(h_i\)层(从第\(1\)层开始算,第\(h_i\)层是楼顶),第\(i\)栋楼的每层都有价值为\(v_i\)的面筋。而\(XM\) 就在楼之间捡面筋。
\(XM\) 可以花费一单位时间向上或向下一层。当然,为了展示他高超的跳楼技巧,他可以从一个楼的某一层跳到相邻楼的同层,如果相邻楼没有当前楼高,\(XM\) 就会跳到相邻楼的楼顶。他在相邻两栋楼之间跳跃一次花费的时间也是一个单位时间。
特别地,因为每栋楼的楼顶没有围栏,当\(XM\) 在楼顶时,爱装*的\(XM\) 可以一下子跳到本栋楼的一楼,而只花费一个单位时间。\(XM\) 捡面筋不需要花费时间。
\(XM\) 现在站在第1栋楼的楼顶津津有味地吃着楼顶的面筋,问题是还有\(m\)个单位时间\(XM\) 就要回去打\(WZ\)(王铮)了,他想知道自己最多能得到多少价值的面筋。
第一行两个正整数\(n\)和\(m\)
下面\(n\)行每行两个正整数\(h_i\)和\(v_i\)
一个正整数,表示\(XM\) 能获得的面筋价值最大值
3 3
2 1
1 5
3 4
14
//喂喂喂,别忘了开long long
对于$10 \(%的数据,\)v_i$ = 1 (聪明的你就知道该输出什么了
对于另外\(10\)%的数据,n = 10
对于另外 \(20\)%的数据,\(n <= 100\)
对于另外$ 20\(%的数据,\)n<=1000$
对于另外\(20\)%的数据,\(n <= 10000\)
对于\(100\)%的数据,\(n,m <= 100000\) , \(h_i,v_i <= 2000\)
每栋楼其实是一个环。不管是从哪一层到达当前楼,花费多长时间,就能得到多少面筋。(忽略从旁边楼过来的时间,我们只看在楼内部花费的时间)。也就是说楼可以任意分割。
知道每栋楼是一个环,这很重要。
那么这其实是一个背包问题,并且由于楼可以分割,这个问题就变成了最简单的贪心买东西问题:
按性价比由高到低排序的背包问题。
不理解吗?就是这个
而且我的数据好像不用开\(longlong\)也行QAQ
在楼间跳跃,跳到下一栋再跳回来?聪明的\(XM\)肯定不会这么做。
我们不确定最后一单位时间\(XM\)在哪一栋楼,所以最外层枚举结束的楼。
确定了最后一栋楼是第\(i\)栋,现在我们要做的就是将前\(i\)栋楼按\(v\)排序,然后在\(m\)允许范围内把\(v\)最大的整栋楼的面筋捡光。当剩余时间不允许捡光整栋楼的时候,能捡多少捡多少(分割物品)。
具体实现时,可以用\(sort\)实现前\(i\)栋楼的有序排列,也可以用priority_queue?
需要注意的是,\(XM\)是在第\(1 - i\)栋楼之间连续跳跃,所以这\(i\)栋楼每一栋都要至少花费1个单位时间(不管他的v是多少)(第一栋楼不需要花费1单位时间)。
对于循环的每一个\(i\),我们得到一个\(ans_i\),最后的\(ans = max(ans_i)\)
复杂度分析:\(O(n^2 log n)\),只能应对\(1000\)以内的数据
#include<cmath>
#include<queue>
#include<cstdio>
#include<string>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn = 100010;
int n, m, tim, usedti;
long long ans, ans0;
struct build{
int h, v, id;
}bd[maxn];
bool operator < (build a, build b){
return a.v < b.v;
}
priority_queue<build> q;
/*
bool cmp(build a, build b){
return a.v > b.v;
}*/
int main(){
freopen("gluten10.in","r",stdin);
freopen("gluten10.out","w",stdout);
scanf("%d%d",&n,&m);
for(int i = 1; i <= n; i++){
scanf("%d%d",&bd[i].h,&bd[i].v);
bd[i].id = i;
bd[i].h --;
}
for(int end = 1; end <= n; end++){
for(int i = 1; i <= end; i++) q.push(bd[i]);
ans0 = 0;
tim = m - end + 1;
usedti = 0;
for(int i = 1; i <= end; i++) ans0 += bd[i].v;
// sort(bd + 1, bd + 1 + end, cmp);
for(int i = 1; i <= end; i++){
int id = q.top().id;
q.pop();
if(usedti + bd[id].h <= tim){
ans0 += bd[id].h * bd[id].v;
usedti += bd[id].h;
}
else{
ans0 += bd[id].v * (tim - usedti);
usedti = tim;
}
}
// cout << end << " " << ans0 << endl;
ans = max(ans, ans0);
}
printf("%d\n",ans);
return 0;
}
上述代码之所以是\(n^2logn\)级别,是因为对于每一次循环,我们都用\(O(n)\)的复杂度将\(1-end\)入队,再用\(O(n)\)计算每一个物品的价值贡献。
正解是这样的:
维护一个小根堆\(q\),堆内元素是楼(结构体build),维护堆内所有楼的面筋总价值和sumv,维护堆内所有楼的层数和sumh(也就是捡完全部楼所要花费的时间)。
每次循环都将当前这栋楼扔小根堆。当\(sumh\) 大于我们能够自由支配的时间(设为\(m0\))时,从小根堆里不断地弹出一些\(v\)值很小的楼。弹出的时候\(sumh\)和\(sumv\)随着减小。如果弹出当前堆内\(v\)最小的楼之后\(sumh\) 要小于我们能自由支配的时间\(m0\),停止弹出,此时\(sumh\)刚好大于\(m0\)
然后计算价值。注意到计算价值时有两种情况。
1)如果一开始\(sumh\)就小于\(m0\),也就是说没有从对内弹出任何元素,那么答案就是\(sumv\)加上经过所有楼一次的时间。
2)否则。当前队内的第一个元素(也就是\(v\)最小的那栋楼)我们分割它,其余队内的楼我们全捡一遍。
#include<cstdio>
#include<cstdlib>
#include<map>
#include<cmath>
#include<ctime>
#include<queue>
#include<string>
#include<cstring>
#include<iostream>
#include<algorithm>
#define ll long long
#define ci cosnt int &
using namespace std;
const int maxn = 1000010;
int n, m, h, v;
ll ans, passgot, sumv, sumh;
struct build{
int h, v;
build(){}
build(int h0, int v0) : h(h0), v(v0){};
};
bool operator < (build a, build b){
return a.v > b.v;
}
priority_queue <build> q;
int main(){
scanf("%d%d",&n,&m);
m++;
for(int i = 1; i <= n; i++){
scanf("%d%d",&h, &v);
m--;
passgot += v;
q.push(build(h-1, v));
sumh += h-1;
sumv += 1ll*v*(h-1);
while(q.size() && sumh - q.top().h >= m){
sumh -= q.top().h;
sumv -= q.top().v * q.top().h * 1ll;
q.pop();
}
/* if(sumh >= m){
ans = max(ans, sumv - (sumh - m) * q.top().v + passgot);
cout << "*" << sumv << endl;
}
else ans = sumv + passgot;
cout << ans << endl;
*/
ans = max(ans, sumh >= m ? sumv - (sumh - m) * q.top().v + passgot : sumv + passgot);
if(m <= 0) break;
}
printf("%lld\n",ans);
return 0;
}
事实上,手写堆的常数比STL堆的常数小很多,可以跑\(10^6\)的数据
#include<bits/stdc++.h>
const int S=1<<20;
char frd[S],*ihead=frd+S;
const char *itail=ihead;
inline char nxtChar()
{
if(ihead==itail)
fread(frd,1,S,stdin),ihead=frd;
return *ihead++;
}
template<class T>
inline void read(T&res)
{
char ch;
while(ch=nxtChar(),! isdigit(ch));
res=ch^48;
while(ch=nxtChar(),isdigit(ch))
res=res*10+ch-48;
}
typedef long long ll;
const int N=1e6+5;
int n;
ll m,tot_h,tot_v,ans;
template<class T>
inline void CkMax(T & x,T y){if(x<y) x=y;}
struct point
{
int v,h;
point(){}
point(int V,int H):
v(V),h(H){}
inline bool operator<(const point & a)const
{
return v<a.v;
}
};
struct Heap
{
point g[N];
int len;
inline void Push(const point & res)
{
int now=++len,nxt=len>>1;
while(nxt)
{
if(res<g[nxt])
g[now]=g[nxt],now=nxt,nxt>>=1;
else break;
}
g[now]=res;
}
inline void Pop()
{
int now=1,nxt=2;
point res=g[len--];
while(nxt<=len)
{
if(nxt<len&&g[nxt|1]<g[nxt]) nxt|= 1;
if(g[nxt]<res)
g[now]=g[nxt],now=nxt,nxt<<=1;
else break;
}
g[now]=res;
}
}Q;
int main()
{
read(n);read(m);++m;
ll res=0;
for(int i=1,h,v;i<=n;++i)
{
read(h);read(v);
--m;
res+=v;
if(!m) break;
if(h>1)
{
point tmp=point(v,h-1);
Q.Push(tmp);
tot_h+=tmp.h;
tot_v+=1ll*tmp.h*tmp.v;
while(Q.len&&tot_h-Q.g[1].h>=m)
{
tot_h-=Q.g[1].h;
tot_v-=1ll*Q.g[1].h*Q.g[1].v;
Q.Pop();
}
}
CkMax(ans,tot_h>=m?
res+tot_v-1ll*Q.g[1].h*Q.g[1].v+1ll*(m-tot_h+Q.g[1].h)*Q.g[1].v:res+tot_v);
}
std::cout<<ans<<std::endl;
return 0;
}
本地测,当\(n = 1e6, m = 1e6, h_i <= 1000, v_i <= 1000\)时,手写堆跑了\(147ms\),STL的优先队列跑了\(1433ms\),大概是\(10\)倍的差距。所以建议学一下手写堆。
原文:https://www.cnblogs.com/ZhengkunJia/p/13325354.html