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\),此条件下,最多做了多少个项目。转移分为不做当前项目,和做当前项目两种情况。即:
时间复杂度\(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;
}