题解 CF1203 F1,F2 Complete the Projects

12.03 在我心目中一直是一个特别的数字呢 QwQ

比赛链接

CF1203F1 Complete the Projects (easy version)

题目链接

暴力做法,是直接枚举全排列。如果想要DP,则必须记录之前已经用过了哪些项目,复杂度势必大于\(2^n\)。故考虑挖掘题目的性质,尝试贪心。如果我们能贪心地求出一个最优(最有可能有解)的排列方式,则直接按照这种排列方式排好序后模拟一遍,就能判断答案是YES或NO了。

我们把项目,按照\(b_i\geq 0\)\(b_i<0\)分为两类。显然,应该先完成所有\(b_i\geq 0\)的项目。于是问题变为两类项目内部应该分别如何排序。

对于\(b_i\geq 0\)的项目,把它们按\(a_i\)从小到大排序。这是因为,随着这类项目的进行,\(r\)的值单调不降,所以把一个\(a_i\)大的项目排在\(a_i\)小的项目前面是没有意义的(交换以后不会变劣)。

对于\(b_i<0\)的项目。首先,为了满足任何项目结束后\(r\)非负,我们令\(a_i=\max(a_i,-b_i)\)。于是接下来只需要考虑每个项目开始前\(r\geq a_i\)这个条件。

我们考虑两个项目\((a_i,b_i)\)\((a_j,b_j)\)\(b_i,b_j<0\)),在何种情况下,把\(i\)排在\(j\)前面更优。

  • 如果把\(i\)排在\(j\)前,则需要满足:\(r\geq a_i\), \(r+b_i\geq a_j\) 。即:\(r\geq \max(a_i,a_j-b_i)\)
  • 如果把\(j\)排在\(i\)前,则需要满足:\(r\geq a_j\), \(r+b_j\geq a_i\) 。即:\(r\geq \max(a_j,a_i-b_j)\)

若要使把\(i\)排在\(j\)前面更优(更有可能有解),则:\(\max(a_i,a_j-b_i)\leq \max(a_j,a_i-b_j)\)

因为\(b_i,b_j<0\),所以\(a_j-b_i>a_j\)。所以后半部分的最大值一定不能取\(a_j\),即:\(a_j<a_i-b_j\ (1)\)

同时,要使得\(\max(a_i,a_j-b_i)\leq a_i-b_j\),即:\(a_i\leq a_i-b_j\ (2)\), \(a_j-b_i\leq a_i-b_j\ (3)\)

因为\(b_j<0\),所以\((2)\)式恒成立。由\((1),(3)\)得:\(a_i+b_i\geq a_j+b_j\)

于是我们得出结论,把\(i\)排在\(j\)前面更优,当且仅当:\(a_i+b_i\geq a_j+b_j\)。也就是说,对于所有\(b_k<0\)的二元组\((a_k,b_k)\),我们将它们按\((a_k+b_k)\)的值从大到小排列即可。

时间复杂度\(O(n\log n)\)

参考代码(片段):

const int MAXN=100;
int n,cur,cnt_po,cnt_ne;
struct node{
	int a,b;
}po[MAXN+5],ne[MAXN+5];
bool cmp_po(node x,node y){return x.a<y.a;}
bool cmp_ne(node x,node y){return x.a+x.b>y.a+y.b;}
int main() {
	read(n);read(cur);
	for(int i=1;i<=n;++i){
		int a,b;read(a);read(b);
		if(b<0){
			++cnt_ne;
			ne[cnt_ne].a=max(a,-b);
			ne[cnt_ne].b=b;
		}
		else{
			++cnt_po;
			po[cnt_po].a=a;
			po[cnt_po].b=b;
		}
	}
	sort(po+1,po+cnt_po+1,cmp_po);
	sort(ne+1,ne+cnt_ne+1,cmp_ne);
	for(int i=1;i<=cnt_po;++i){
		if(cur<po[i].a){puts("NO");return 0;}
		cur+=po[i].b;
	}
	for(int i=1;i<=cnt_ne;++i){
		if(cur<ne[i].a){puts("NO");return 0;}
		cur+=ne[i].b;
	}
	puts("YES");
	return 0;
}

CF1203F2 Complete the Projects (hard version)

题目链接

根据上一题中的分析,我们仍然可以把所有项目,按\(b_i\geq 0\)\(b_i<0\)分为两类。且所有第一类项目排在所有第二类项目前面。

对于\(b_i\geq0\)的项目,我们只需要把它们按\(a_i\)从小到大排序后,贪心地能选则选。因为选了当前项目,能使答案增加,且\(r\)不会减少。

对于\(b_i<0\)的项目,我们仍然把它们按照\((a_i+b_i)\)从大到小排列。现在相当于从中选出一个子序列(不用改变选出的元素的顺序),使其合法。

考虑DP。设\(dp[i][j]\)表示考虑了(排序后的)前\(i\)个(\(b_k<0\)的)项目,完成第\(i\)个项目后,rating为\(j\),此条件下,最多做了多少个项目。转移分为不做当前项目,和做当前项目两种情况。即:

\[dp[i][j]=\max(dp[i-1][j],dp[i-1][j-b_i]+1) \]

时间复杂度\(O(n\log n+nm)\)。其中\(m\)表示整个过程中的最大rating,\(m\leq r+nb\leq 60000\)

参考代码:

//problem:CF1203F2
#include <bits/stdc++.h>
using namespace std;

#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fi first
#define se second
#define SZ(x) ((int)(x).size())

typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;

namespace Fread{
const int MAXN=1<<20;
char buf[MAXN],*S,*T;
inline char getchar(){
	if(S==T){
		T=(S=buf)+fread(buf,1,MAXN,stdin);
		if(S==T)return EOF;
	}
	return *S++;
}
}//namespace Fread
#ifdef ONLINE_JUDGE
	#define getchar Fread::getchar
#endif
template<typename T>inline void read(T& x){
	x=0;int f=1;
	char ch=getchar();
	while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
	while(isdigit(ch))x=x*10+(ch-'0'),ch=getchar();
	x*=f;
}
/*  ------  by:duyi  ------  */ // myt天下第一
const int MAXN=100,MAXR=30000;
int n,cur,cnt_po,cnt_ne,ans,dp[MAXN+5][MAXR*2+5];
struct node{
	int a,b;
}po[MAXN+5],ne[MAXN+5];
bool cmp_po(node x,node y){return x.a<y.a;}
bool cmp_ne(node x,node y){return x.a+x.b>y.a+y.b;}
int main() {
	read(n);read(cur);
	for(int i=1;i<=n;++i){
		int a,b;read(a);read(b);
		if(b<0){
			++cnt_ne;
			ne[cnt_ne].a=max(a,-b);
			ne[cnt_ne].b=b;
		}
		else{
			++cnt_po;
			po[cnt_po].a=a;
			po[cnt_po].b=b;
		}
	}
	sort(po+1,po+cnt_po+1,cmp_po);
	sort(ne+1,ne+cnt_ne+1,cmp_ne);
	for(int i=1;i<=cnt_po;++i){
		if(cur<po[i].a)continue;
		cur+=po[i].b;ans++;
	}
	assert(cur<=MAXR*2);
	dp[0][cur]=ans;
	for(int i=1;i<=cnt_ne;++i){
		for(int j=0;j<=cur;++j){
			dp[i][j]=dp[i-1][j];
			if(j-ne[i].b<=cur&&j-ne[i].b>=ne[i].a){
				dp[i][j]=max(dp[i][j],dp[i-1][j-ne[i].b]+1);
			}
		}
	}
	for(int i=0;i<=cur;++i)ans=max(ans,dp[cnt_ne][i]);
	cout<<ans<<endl;
	return 0;
}

猜你喜欢

转载自www.cnblogs.com/dysyn1314/p/12659433.html