1 游戏通关
problem
小明需要完成(N)个任务才能将这个游戏通关。
每个任务完成时限(T),就是这个任务必须在时间(T)之前完成(你可以认为游戏刚开始的时间为1),还有完成这个任务小明可以获得一定的奖励(W)。由于小明娴熟的技术以及任务的简单,他可以在一个单位时间将任务完成。
小明想要在老师到来之前将任务全部完成,同时他也想获得最多的奖励。
(N≤2 imes 10^5,T_i≤2 imes 10^5,W_i≤2 imes 10^3)
solution
这道题和智力大冲浪相同,只是将最小减少奖励改为最大增加奖励;
奖励从大到小排序,并且在可行区间内尽量往后放。
不过数据范围较大,如果暴力枚举1~t[i]大概会超时。
优先队列优化。
code
#include <iostream>
#include <algorithm>
#include <cstdio>
using namespace std;
long long read(){
long long x=1,a=0;char ch=getchar();
while (ch<'0'||ch>'9'){if (ch=='-') x=-1;ch=getchar();}
while (ch>='0'&&ch<='9'){a=a*10+ch-'0';ch=getchar();}
return x*a;
}
#include<queue>
const int maxn = 200005;
struct node{
int t,v;
}a[maxn];
priority_queue<int>q;
bool cmp(const node x,const node y){
return x.t > y.t;
}
int n,ttt,ans,js;
int main(){
n=read();
for(int i = 1;i <= n;i++)
a[i].t=read(),a[i].v=read(),ttt=max(a[i].t,ttt);
sort(a+1,a+n+1,cmp);
for(int i = ttt;i >= 1;i --)
{
while(a[++ js].t >= i)q.push(a[js].v);
js --;
if(!q.empty())
{
ans += q.top();
q.pop();
}
}
printf("%d",ans);
return 0;
}
2 优惠券
problem
新学期开学了,Smart准备买一些新书,书店有(N)本新书,第(i)本新书价格为(P_i),使用优惠券购买第(i)本书时价格会降为(C_i)。Smart有(K)张优惠券,每本新书只能使用一次优惠券。Smart想知道花不超过M的钱最多可以买多少本新书?
solution
要买的最多,那一定是先买优惠券价最便宜的。
如果优惠券没用完,钱就花光了,当前买的书即为答案。
如果钱还有剩,那么对于剩下的n-k本书,判断是用原价买它,或把另一本书的优惠券给它而另一本原件买比较便宜。
但是若枚举所有已经使用优惠券的书,时间复杂度显然不友好。
交换条件为c[j]+p[i] < c[i]+p[j]
,即p[i]-c[i] < p[j]-c[j]
因此,优先队列要维护的是:原价和优惠券价的差值,从小到大排。每当对某本书使用优惠券时,把它的差值压入优先队列。
code
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <queue>
using namespace std;
long long read(){
long long x=1,a=0;char ch=getchar();
while (ch<'0'||ch>'9'){if (ch=='-') x=-1;ch=getchar();}
while (ch>='0'&&ch<='9'){a=a*10+ch-'0';ch=getchar();}
return x*a;
}
const int maxn=5e4+10;
struct node{
int p,c;
}a[maxn];
long long n,k,m;
bool cmp(node x,node y){return x.c<y.c;}
priority_queue<int,vector<int>,greater<int> > q1;
priority_queue<pair<int,int>,vector<pair<int,int> >,greater<pair<int,int> > >q2,q3;
int book[maxn];
int main(){
n=read(),k=read(),m=read();
for(int i=1;i<=n;i++) a[i].p=read(),a[i].c=read();
sort(a+1,a+n+1,cmp);
long long now=0;
for(int i=1;i<=k;i++){
now+=a[i].c;
if(now>m) {printf("%d
",i-1);return 0;}
q1.push(a[i].p-a[i].c);
}
if(k==n){printf("%d
",n);return 0;}
for(int i=k+1;i<=n;i++){
q2.push(make_pair(a[i].c,i));
q3.push(make_pair(a[i].p,i));
}
int ans=k;
for(int i=k+1;i<=n;i++){
while(book[q2.top().second]) q2.pop();
while(book[q3.top().second]) q3.pop();
int p1=q2.top().second;
int p2=q3.top().second;
int tmp1=q2.top().first+q1.top();
int tmp2=q3.top().first;
if(tmp1<tmp2){
now+=tmp1;q1.pop();q2.pop();q1.push(a[p1].p-a[p1].c);
book[p1]=1;
}
else{
now+=tmp2;
q3.pop();book[p2]=1;
}
ans++;
if(now>m){printf("%d
",ans-1);return 0;}
}
printf("%d
",n);
return 0;
}
3 探讨人生
problem
Smart每次他与好友A探讨人生要花费(a)个小时,并可以得到(x)点人生经验;每次与好友(B)探讨人生要花费(b)个小时,并得到(y)点人生经验。但是Smart的精力是有限的,他只能抽出(n)个小时来跟他的友人们探讨人生,若这(n)个小时并没有被用完,则Smart会把剩下的时间拿来跟好友C聊天,而这并不能得到人生经验。现在Smart想知道,他最多可以得到多少点人生经验。
【没数据范围】
solution
因为只有两个人,所以暴力枚举探讨次数就可以了。
剪枝:先swap一下,保证与A探讨单位时间经验更多。
为了使枚举的探讨次数控制在(sqrt{n})以内,需要判断一下:若(a>sqrt{n}),则枚举次数一定小于(sqrt{n}),正常枚举与(a)探讨次数即可。否则,枚举与B探讨次数。
因为与A探讨更优,即与A探讨(b)次优于与B探讨(a)次,所以当枚举与B探讨次数大于等于(a)时,一定不如把其中的(a)次变成和A探讨。
所以最多只要枚举(a)次。
code
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#define ll long long
using namespace std;
ll read() {
ll a = 0, x = 1;
char ch = getchar();
while (ch > '9' || ch < '0') {
if (ch == '-')
x = -1;
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
a = a * 10 + ch - '0';
ch = getchar();
}
return a * x;
}
ll n, x, y, a, b, maxn = 0;
int main() {
n = read(), x = read(), y = read(), a = read(), b = read();
if (x * 1.0 / a < y * 1.0 / b) {
swap(a, b);
swap(x, y);
}
if (a >= sqrt(n)) {
for (int i = 0; i * a <= n; i++) {
maxn = max(maxn, i * x + (n - i * a) / b * y);
}
} else {
for (int i = 0; i <= a && i * b <= n; i++) {
maxn = max(maxn, i * y + (n - i * b) / a * x);
}
}
printf("%lld", maxn);
return 0;
}
4 数字游戏
problem
小W发明了一个游戏,他在黑板上写出一行数字(a_1,a_2,…a_n(n≤200)),然后给你(m)个回合的机会,每个回合你可以从中选一个数擦除它,接着剩下来的每个数字(a_i)都要递减一个值(b_i)。如此重复(m)个回合,所有你擦除的数字之和就是你得到的分数。
编程帮小W算算,对于每个给出的(a_n)和(b_n)序列,可以得到的最大得分是多少?
solution
贪心得到dp枚举顺序。
当m=n,即全选时,显然按b从大到小选。
m<n时,需要用dp解决,但策略是相同的,即把b从大到小排序。
设f[j]表示选j个数得到的最大价值。
状态转移方程:f[j] = max(f[j-1]+a[i]-b[i]*(j-1))
code
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <queue>
using namespace std;
long long read(){
long long x=1,a=0;char ch=getchar();
while (ch<'0'||ch>'9'){if (ch=='-') x=-1;ch=getchar();}
while (ch>='0'&&ch<='9'){a=a*10+ch-'0';ch=getchar();}
return x*a;
}
const int maxn=201;
struct node{
int a,b;
}num[maxn];
int n,m;
int f[2][N];//f[i][j]表示前i个数中取了j个数的最优解
//f[i][j]=max(f[i-1][j],f[i-1][j-1]+(a[i]-b[i])*(j-1))
void init(){
n=read(),m=read();
for(int i=1;i<=n;i++) num[i].a=read();
for(int i=1;i<=n;i++) num[i].b=read();
}
bool cmp(node p,node q){return p.b>q.b; }
void work(){
sort(num+1,num+1+n,cmp);
memset(f,0,sizeof(f));
for(int i=1;i<=n;i++)
for(int j=1;j<=min(i,m);j++)
f[i%2][j]=max(f[(i+1)%2][j],f[(i+1)%2][j-1]+num[i].a-num[i].b*(j-1));
printf("%d
",f[n%2][m]);
}
int main(){
init();work();return 0;
}