1377.竞赛真理

原题链接

外网进不去

题目大意

有一次考试,一共有 n ( 3 ≤ n ≤ 30 ) n(3\le n\le 30) n(3n30)题,有 t ( 2 ≤ t ≤ 108000 ) t(2\le t\le 108000) t(2t108000)秒,做题有两种方式,一种是骗分,一种是认真做,每一种,都对应有所需时间( 1 ≤ T 1 i , T 2 i ≤ T 1\le T1_i,T2_i\le T 1T1i,T2iT),和可得分数( 2 ≤ W 1 i , W 2 i ≤ 30000 2\le W1_i,W2_i\le 30000 2W1i,W2i30000),要求求出最多的得分。

解题思路

方法1

我们可以这么思考,每一个题目,都有骗分、认真做和不做三种选择,所以,我们可以使用dfs(深搜)来做这题,简单算一下,时间复杂度大约为 O ( 3 30 ) O(3^{30}) O(330),也就是 O ( 205891132094649 ) O(205891132094649) O(205891132094649)不用想也知道会超时。这时,我们就该想到剪枝了。
首先是比较容易想的一种可行性剪枝:时间是否可行,也就是剩余时间是否大于下一个骗分或认真做的时间。

if(t>=a[dep].t1) dfs(dep+1,t-a[dep].t1,fs+a[dep].w1);
if(t>=a[dep].t2) dfs(dep+1,t-a[dep].t2,fs+a[dep].w2);

接着就是比较难的一种可行性剪枝。假设我们算出每一题的得分率(乘上时间便是得分),在认真做和骗分之间取最大,之后再排个序(从大到小),那么每一个位置后面的题目的得分率都会小于或等于它,也就是说,后面的题不管怎样做,都没有这一个题的得分率乘剩余时间得到分数高,由此又一个剪枝出现了:

if(t*a[dep].p+fs<ans) return;

注意:在初始化时,排序的应该是骗分的得分率与认真做题的得分率中的最大值!

if(w_1*1.0/t_1>w_2*1.0/t_2)
	a[i].p=w_1*1.0/t_1;//转换小数,再除
else
	a[i].p=w_2*1.0/t_2;//转换小数,再除

代码实现

#include<iostream>
#include<fstream>
#include<cstring>
#include<string>
#include<vector>
#include<stack>
#include<queue>
#include<algorithm>
#include<cmath>
#include<cstdio>
#include<map>
#include<ctime>
using namespace std;
struct node{
    
    
	double p;
	int w1,t1,w2,t2;
} a[100];//定义结构体
int n,t,w_1,t_1,w_2,t_2,ans;
void dfs(int dep,int t,int fs)
{
    
    
	if(dep>n){
    
    
		ans=max(ans,fs);//找出答案
		return;
	}
	if(t*a[dep].p+fs<ans) return;//可行性剪枝
	if(t>=a[dep].t1) dfs(dep+1,t-a[dep].t1,fs+a[dep].w1);//可行性剪枝
	if(t>=a[dep].t2) dfs(dep+1,t-a[dep].t2,fs+a[dep].w2);//可行性剪枝
	dfs(dep+1,t,fs);
	return;
}
bool pd(node q,node h)
{
    
    
	return q.p>h.p;//结构体排序必备
}
int main()
{
    
    
	scanf("%d %d",&n,&t);
	for(int i=1;i<=n;i++){
    
    
		scanf("%d %d %d %d",&w_1,&t_1,&w_2,&t_2);
		a[i].w1=w_1,a[i].w2=w_2,a[i].t1=t_1,a[i].t2=t_2;
		if(w_1*1.0/t_1>w_2*1.0/t_2)
			a[i].p=w_1*1.0/t_1;//转换小数,再除
		else
			a[i].p=w_2*1.0/t_2;//转换小数,再除
	}
	sort(a+1,a+n+1,pd);//排序,位于"#include<algorithm>"中
	dfs(1,t,0);
	cout<<ans;
	return 0;
}

方法2

我们可以将这一题看为“有一个背包,一共有 n ( 3 ≤ n ≤ 30 ) n(3\le n\le 30) n(3n30)个物品,有 t ( 2 ≤ t ≤ 108000 ) t(2\le t\le 108000) t(2t108000)的空间,每一个东西有重要性,选择物品,使重要性之和最大”的题,这样,这题就可以变成为一道背包问题了。但这一题和普通的背包问题不太一样,因为在骗分和认真做之间,只能选择一种,所以,我们要在两种之间选择更优的:

for(int i=1;i<=n;i++){
    
    //枚举题目
	for(int j=t;j>=1;j--){
    
    //枚举时间
		if(j-a[i][1]>=0)//出界条件,是否可行(防止RE)
			b[j]=max(b[j],b[j-a[i][1]]+a[i][0]);//状态转移方程(认真做与原来比)
		if(j-a[i][3]>=0)//出界条件,是否可行(防止RE)
			b[j]=max(b[j],b[j-a[i][3]]+a[i][2]);//状态转移方程(骗分与认真做比,因为上一次已经得到了认真做与原来比的最优值)
	}
}

代码实现

#include<iostream>
#include<fstream>
#include<cstdio> 
#include<cmath>
#include<cstring>
#include<string>
#include<algorithm>
#include<vector>
#include<queue>
#include<stack>
#include<map>
#include<ctime>
#include<ios>
#include<set>
using namespace std;
long long t,n,s,m,a[1000000][4],b[1000000],ans;
int main()
{
    
    
	ios::sync_with_stdio(false); //加快输入输出,建议加
	cin>>n>>t;
	for(int i=1;i<=n;i++)
		cin>>a[i][0]>>a[i][1]>>a[i][2]>>a[i][3];
	for(int i=1;i<=n;i++){
    
    //枚举题目
		for(int j=t;j>=1;j--){
    
    //枚举时间
			if(j-a[i][1]>=0)//出界条件,是否可行(防止RE)
				b[j]=max(b[j],b[j-a[i][1]]+a[i][0]);//状态转移方程(认真做与原来比)
			if(j-a[i][3]>=0)//出界条件,是否可行(防止RE)
				b[j]=max(b[j],b[j-a[i][3]]+a[i][2]);//状态转移方程(骗分与认真做比,因为上一次已经得到了认真做与原来比的最优值)
		}
	}
	cout<<b[t];
}

样例1

输入

4 10800
18 3600 3 1800
22 4000 12 3000
28 6000 0 3000
32 8000 24 6000

输出

50

样例2

输入

3 7200
50 5400 10 900
50 7200 10 900
50 5400 10 900

输出

70

おすすめ

転載: blog.csdn.net/weixin_41247488/article/details/119796421