zoukankan      html  css  js  c++  java
  • 题解 CF1316E and UOJ495:一类结合贪心的背包问题

    CF1316E Team Building

    题目链接

    如果只选观众,则我们取(a_i)最大的(k)个人就好。如果只选运动员,则我们可以做一个简单的状压DP:设(dp[i][mask])表示考虑了前(i)个人,(maskin[0,2^p))中的这些位置已经被占,此时的最大收益。DP复杂度(O(n2^pp))

    考虑如何把这两种做法结合。

    发现如果确定了选择哪些运动员,则观众一定从剩下的人中从大往小依次选前(k)个。

    所以我们可以把人按“观众值”(a_i)从大到小排序。然后做上述的状压DP。比方说在前(i-1)个人中,已经选出了(j)个运动员,那么若(i-jleq k),则第(i)个人如果不是运动员,就一定是观众;否则第(i)个人只能要么作为运动员,要么两个都不是。

    时间复杂度(O(n2^pp))

    参考代码:

    //problem:CF1316E
    #include <bits/stdc++.h>
    using namespace std;
    
    #define pb push_back
    #define mk make_pair
    #define lob lower_bound
    #define upb upper_bound
    #define fst first
    #define scd second
    
    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
    inline int read(){
    	int f=1,x=0;char ch=getchar();
    	while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
    	while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
    	return x*f;
    }
    inline ll readll(){
    	ll f=1,x=0;char ch=getchar();
    	while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
    	while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
    	return x*f;
    }
    /*  ------  by:duyi  ------  */ // myt天下第一
    const int MAXN=1e5;
    int n,p,K;
    ll dp[MAXN+5][1<<7];
    struct node{
    	int a,s[7];
    	bool operator<(const node &b){
    		return a>b.a;
    	}
    }a[MAXN+5];
    int main() {
    	n=read();p=read();K=read();
    	for(int i=1;i<=n;++i)a[i].a=read();
    	for(int i=1;i<=n;++i)for(int j=0;j<p;++j)a[i].s[j]=read();
    	sort(a+1,a+n+1);
    	memset(dp,0xcf,sizeof(dp));
    	dp[0][0]=0;
    	for(int i=1;i<=n;++i){
    		for(int j=0;j<(1<<p);++j){
    			for(int k=0;k<p;++k)if(!(j&(1<<k))){
    				dp[i][j|(1<<k)]=max(dp[i][j|(1<<k)],dp[i-1][j]+a[i].s[k]);
    			}
    			if(i-__builtin_popcount(j)<=K){
    				dp[i][j]=max(dp[i][j],dp[i-1][j]+a[i].a);
    			}
    			else dp[i][j]=max(dp[i][j],dp[i-1][j]);
    		}
    	}
    	cout<<dp[n][(1<<p)-1]<<endl;
    	return 0;
    }
    

    UOJ495 新年的促销

    题目链接

    暴力的DP做法:

    (dp[i][j][k][l])表示考虑了前(i)件物品,总共花了(j)元钱,花钱买了(k)袋大米,白嫖了(l)袋大米时获得的最大重量。统计答案时判断(k)(l)的关系即可。

    时间复杂度(O(n^3m))。期望得分(60)分。

    正解做法:

    尝试挖掘题目中的性质。发现:

    • 性质一:任意一袋花钱买的大米的价格,一定小于等于任意一袋白嫖的大米的价格。证明:否则我们把白嫖的比买的更便宜的那一袋大米买下来,把买的比白嫖的更贵的那一袋大米用来白嫖,总重量不变,花的钱变少了。
    • 性质二:当已经确定了哪些大米是花钱买来的时,白嫖的大米一定从没买的大米中选重量最大的那几袋。正确性显然。

    把所有大米按价格从小到大排序。根据性质一,考虑枚举花钱买的大米中最贵的是哪一袋。边枚举边DP。设(dp[i][j][k])表示枚举了价格前(i)小的物品,买了(j)个,花了(k)元钱,获得的最大重量。根据性质二,赠品可以直接在后(n-i)个物品里选重量前若干大的。即每枚举一个(i),都把后面的物品按重量排个序,然后更新所有(m)的答案。

    时间复杂度(O(n^2m+n^2log n))

    参考代码:

    //problem:uoj495
    #include <bits/stdc++.h>
    using namespace std;
    
    #define pb push_back
    #define mk make_pair
    #define lob lower_bound
    #define upb upper_bound
    #define fst first
    #define scd second
    
    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++;
    	}
    }
    #ifdef ONLINE_JUDGE
    	#define getchar Fread::getchar
    #endif
    inline int read(){
    	int f=1,x=0;char ch=getchar();
    	while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
    	while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
    	return x*f;
    }
    inline ll readll(){
    	ll f=1,x=0;char ch=getchar();
    	while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
    	while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
    	return x*f;
    }
    /*  ------  by:duyi  ------  */ // dysyn1314
    inline void cmax(int &x,int y){x=x<y?y:x;}
    const int MAXN=300,MAXM=1000;
    struct item{int w,p;}a[MAXN+5],b[MAXN+5];
    bool cmp_p(item a,item b){return a.p<b.p;}
    bool cmp_w(item a,item b){return a.w<b.w;}
    int n,m,A,B,dp[MAXN+5][MAXN+5][MAXM+5],suf[MAXN+5],ans[MAXM+5];
    int main() {
    	n=read();m=read();A=read();B=read();
    	for(int i=1;i<=n;++i)a[i].w=read();
    	for(int i=1;i<=n;++i)a[i].p=read();
    	sort(a+1,a+n+1,cmp_p);
    	memset(dp,0xcf,sizeof(dp));
    	dp[0][0][0]=0;
    	for(int i=1;i<=n;++i){
    		for(int j=0;j<=n;++j){
    			for(int k=0;k<=m;++k){
    				cmax(dp[i][j][k],dp[i-1][j][k]);
    				if(j>=1&&k>=a[i].p)cmax(dp[i][j][k],dp[i-1][j-1][k-a[i].p]+a[i].w);
    			}
    		}
    		for(int j=i+1;j<=n;++j)b[j]=a[j];
    		sort(b+i+1,b+n+1,cmp_w);
    		for(int j=n;j>=i+1;--j)suf[j]=suf[j+1]+b[j].w;
    		for(int j=1;j<=i;++j){
    			for(int k=1;k<=m;++k){
    				cmax(ans[k],dp[i][j][k]+suf[max(i+1,n-(j/A+j/B)+1)]);
    			}
    		}
    	}
    	for(int i=1;i<=m;++i){
    		cmax(ans[i],ans[i-1]);
    		printf("%d ",ans[i]);
    	}
    	puts("");
    	return 0;
    }
    

    小结与思考

    当题目的条件较为复杂时,传统的背包DP往往需要增加很多维度,造成时间复杂度不够优秀。

    此时就需要我们仔细发掘题目的性质,找到可以贪心的地方。把一部分条件用贪心处理。这样就大大提高了算法效率。

  • 相关阅读:
    网页爬虫小记:两种方式的爬取网站内容
    AOP中使用Aspectj对接口访问权限进行访问控制
    Spring Boot应用总结更新
    SpringBoot集成mybatis配置
    经验收集
    关于阿拉伯文开发的一点经验
    关于IDataReader.GetSchemaTable的一些事情
    removing vmware debugger from visual studio
    SQL Server 2008 R2 附加数据库 “尝试打开或创建物理文件 拒绝访问”的解决办法
    Visual Studio 2013 ReportViewer Control
  • 原文地址:https://www.cnblogs.com/dysyn1314/p/12527703.html
Copyright © 2011-2022 走看看