12.03 在我心目中一直是一个特别的数字呢 QwQ
CF1203F1 Complete the Projects (easy version)
暴力做法,是直接枚举全排列。如果想要DP,则必须记录之前已经用过了哪些项目,复杂度势必大于(2^n)。故考虑挖掘题目的性质,尝试贪心。如果我们能贪心地求出一个最优(最有可能有解)的排列方式,则直接按照这种排列方式排好序后模拟一遍,就能判断答案是YES或NO了。
我们把项目,按照(b_igeq 0)和(b_i<0)分为两类。显然,应该先完成所有(b_igeq 0)的项目。于是问题变为两类项目内部应该分别如何排序。
对于(b_igeq 0)的项目,把它们按(a_i)从小到大排序。这是因为,随着这类项目的进行,(r)的值单调不降,所以把一个(a_i)大的项目排在(a_i)小的项目前面是没有意义的(交换以后不会变劣)。
对于(b_i<0)的项目。首先,为了满足任何项目结束后(r)非负,我们令(a_i=max(a_i,-b_i))。于是接下来只需要考虑每个项目开始前(rgeq a_i)这个条件。
我们考虑两个项目((a_i,b_i))和((a_j,b_j))((b_i,b_j<0)),在何种情况下,把(i)排在(j)前面更优。
- 如果把(i)排在(j)前,则需要满足:(rgeq a_i), (r+b_igeq a_j) 。即:(rgeq max(a_i,a_j-b_i)) 。
- 如果把(j)排在(i)前,则需要满足:(rgeq a_j), (r+b_jgeq a_i) 。即:(rgeq 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_ileq a_i-b_j (2)), (a_j-b_ileq a_i-b_j (3))。
因为(b_j<0),所以((2))式恒成立。由((1),(3))得:(a_i+b_igeq a_j+b_j)。
于是我们得出结论,把(i)排在(j)前面更优,当且仅当:(a_i+b_igeq a_j+b_j)。也就是说,对于所有(b_k<0)的二元组((a_k,b_k)),我们将它们按((a_k+b_k))的值从大到小排列即可。
时间复杂度(O(nlog 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_igeq 0)和(b_i<0)分为两类。且所有第一类项目排在所有第二类项目前面。
对于(b_igeq0)的项目,我们只需要把它们按(a_i)从小到大排序后,贪心地能选则选。因为选了当前项目,能使答案增加,且(r)不会减少。
对于(b_i<0)的项目,我们仍然把它们按照((a_i+b_i))从大到小排列。现在相当于从中选出一个子序列(不用改变选出的元素的顺序),使其合法。
考虑DP。设(dp[i][j])表示考虑了(排序后的)前(i)个((b_k<0)的)项目,完成第(i)个项目后,rating为(j),此条件下,最多做了多少个项目。转移分为不做当前项目,和做当前项目两种情况。即:
时间复杂度(O(nlog n+nm))。其中(m)表示整个过程中的最大rating,(mleq r+nbleq 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;
}