20201026day46 刷题记录

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;	
}
原文地址:https://www.cnblogs.com/liuziwen0224/p/20201026day26-001.html