zoukankan      html  css  js  c++  java
  • 贪心小结

    写在前面

    首先衷心地感谢(psj)学长能够在百忙之中抽出时间为我们的省选讲课%%%(虽然蒟蒻还没有省选的水平),感谢!!!

    贪心是基础算法,应用广泛,结合性强,可以与许多算法综合考察。一般来说,有了贪心,一道题的思维难度上去了。这几天做的贪心,不同于以往的纯思维,而是思维+码力,相对来说综合性强一些。

    一些题目。

    CF545C Woodcutters

    Link

    Solution

    经典的选不相交区间的变式。我们先要把模型建立起来。

    树位于位置(pos) ,树高(h) ,那我们就可以自然的想到将一棵树的两个倒法转化成两个区间,貌似这道题就做完了。

    但是,一棵树即使不砍,它也会在那里,阻碍你砍其他的树,所以我们在构造区间时,要判断这棵树向左/右倒的时候会不会砸到左/右边的树。会砸到,这个区间就不能构造出来。

    还有一个小小的地方,由于一棵树不能同时向两边倒,所以区间是([pos-h, pos])([pos,pos+h]) 。让它们有交点。

    Code

    #include<bits/stdc++.h>
    #define N (200010)
    #define M (100010)
    using namespace std;
    struct xbk{int l,r;}q[N];//区间
    struct xll{int p,l;}a[M];//树
    int n,nl=-2e9-2,cnt,ans;
    inline int read(){
    	int w=0;
    	char ch=getchar();
    	while(ch>'9'||ch<'0') ch=getchar();
    	while(ch>='0'&&ch<='9'){
    		w=(w<<3)+(w<<1)+(ch^48);
    		ch=getchar();
    	}
    	return w;
    }
    inline bool cmp(xbk a,xbk b){return a.r<b.r;}
    inline bool cmp1(xll a,xll b){return a.p<b.p;}
    int main(){
    	n=read();
    	for(int i=1;i<=n;i++) a[i].p=read(),a[i].l=read();
    	sort(a+1,a+1+n,cmp1);
    	a[0].p=-1e9-2,a[n+1].p=2e9+10;
    	for(int i=1;i<=n;i++){
    		if(a[i].p-a[i].l>a[i-1].p) q[++cnt].l=a[i].p-a[i].l,q[cnt].r=a[i].p;
    		if(a[i].p+a[i].l<a[i+1].p) q[++cnt].l=a[i].p,q[cnt].r=a[i].p+a[i].l;
    	}
    	sort(q+1,q+1+cnt,cmp);
    	/*
    	for(int i=1;i<=cnt;i++){
    		cout<<q[i].l<<" "<<q[i].r<<endl;
    	}*/
      //求不相交区间
    	for(int i=1;i<=cnt;i++){
    		if(q[i].l>nl){
    			nl=q[i].r;
    			ans++;
    		}
    	}
    	printf("%d
    ",ans);
    	return 0;
    }
    

    CF140C New Year Snowmen

    Link

    Solution

    贪心策略:每次取最多的三个值组成雪人。

    感性理解

    设答案的上界为(Maxans)

    第一多的数的个数是(max)

    第二多的数的个数是(semax)

    (Maxans=minegin{cases}leftlfloordfrac{n}{3} ight floor\ leftlfloor frac{n-mac}{2} ight floor \ n-max-semaxend{cases})

    好像输出这个是对的

    好啦做法有了,接下来就是模拟实现了(≧▽≦)/啦啦啦

    排序去重离散化,三位一体

    每次取最多的三个,(num--) ,还有就丢回去,直到没了为止

    输出答案记得排序~~

    Code

    #include<bits/stdc++.h>
    using namespace std;
    struct xbk{
    	int id,num;
    	bool operator < (const xbk &x) const{
    		return num<x.num;
    	}
    }a[1000011];
    struct xll{int x,y,z;}ans[111111];
    int n,m,cnt,x[111111];
    map<int,int>mp;
    priority_queue<xbk>q;
    inline void msort(xll &x){
    	if(x.x<x.y) swap(x.x,x.y);
    	if(x.x<x.z) swap(x.x,x.z);
    	if(x.y<x.z) swap(x.y,x.z);
    }
    int main(){
    	scanf("%d",&n);
    	for(int i=1;i<=n;i++) scanf("%d",&x[i]),mp[x[i]]++;
    	sort(x+1,x+1+n);
    	m=unique(x+1,x+1+n)-x-1;
    	for(int i=1;i<=m;i++){
    		a[i].num=mp[x[i]];
    		a[i].id=i;
    		q.push(a[i]);
    	}
    	while(q.size()>=3){
    		xbk A=q.top();q.pop();
    		xbk B=q.top();q.pop();
    		xbk C=q.top();q.pop();
    		if(!A.num||!B.num||!C.num) break;
    		if(A.id==B.id||B.id==C.id||A.id==C.id) continue;
    		ans[++cnt].x=x[A.id];
    		ans[cnt].y=x[B.id];
    		ans[cnt].z=x[C.id];
    		A.num--,B.num--,C.num--;
    		if(A.num) q.push(A);
    		if(B.num) q.push(B);
    		if(C.num) q.push(C);
    	}
    	printf("%d
    ",cnt);
    	for(int i=1;i<=cnt;i++){
    		msort(ans[i]);
    		printf("%d %d %d
    ",ans[i].x,ans[i].y,ans[i].z);
    	}
    	return 0;
    }
    

    JXOI2017 加法

    Link

    Solution

    贪心+二分答案好题

    二分答案,再去扫一遍数组,遇到一个小于(mid)的,就选能覆盖到这个位置的区间中尽量靠后的,为后面的提供便利。

    注意一定别忘了操作数是有限制的。

    细节看注释。

    Code

    #include<bits/stdc++.h>
    #define ll long long
    #define N (200010)
    using namespace std;
    struct xbk{
    	int l,r;
    	bool operator < (const xbk &b) const{
    		return r<b.r;
    	}
    }q[N];
    int T,n,m;
    ll ad,k,ans,c[N],a[N],aa[N];
    inline ll read(){
    	ll w=0;
    	char ch=getchar();
    	while(ch>'9'||ch<'0') ch=getchar();
    	while(ch>='0'&&ch<='9'){
    		w=(w<<3)+(w<<1)+(ch^48);
    		ch=getchar();
    	}
    	return w;
    }
    inline int lb(int x){return x&(-x);}
    inline bool cmp(xbk a,xbk b){return a.l<b.l;}
    inline void add(int pos,int val){
    	for(int i=pos;i<=n;i+=lb(i)) c[i]+=val;
    	return;
    }
    inline ll sum(int pos){
    	ll res=0;
    	for(int i=pos;i;i-=lb(i)) res+=c[i];
    	return res;
    }
    inline bool judge(ll val){
    	priority_queue<xbk>qq;
    	int now=0,cnt=0;
    	memset(c,0,sizeof(c));
    	for(int i=1;i<=n;i++) aa[i]=a[i];
    	for(int i=1;i<=n;i++){
    		if(!qq.empty())
    			if(qq.top().r<i)//右端点小于i的已经失去利用价值
    				while(!qq.empty()) qq.pop();
          //左端点符合条件的加进来
    		while(q[now+1].l<=i&&now<m){
    			now++;
    			qq.push(q[now]);
    		}
    		while(aa[i]+sum(i)<val&&!qq.empty()){
    			xbk nw=qq.top();
    			qq.pop();
    			if(nw.r<i) return false;
              //用差分+树状数组维护区间加
    			add(nw.l,ad),add(nw.r+1,-ad);
    			cnt++;
    			if(cnt>k) return false;
              //超过次数限制
    		}
          //没得区间选了,但还是小于,说明不行
    		if(aa[i]+sum(i)<val) return false;
    	}
    	return true;
    }
    int main(){
    	T=read();
    	while(T--){
    		ll l=1e9,r=0;
    		n=read(),m=read(),k=read(),ad=read();
    		for(int i=1;i<=n;i++) a[i]=read(),r=max(a[i],r),l=min(l,a[i]);
    		for(int i=1;i<=m;i++) q[i].l=read(),q[i].r=read();
    		sort(q+1,q+1+m,cmp);
          //按左端点排序
    		r+=1e9;
          //其实右边界最好是k*ad+l
    		while(l<=r){
    			ll mid=(l+r)>>1;
    			if(judge(mid)) ans=mid,l=mid+1;
    			else r=mid-1;
    		}
    		printf("%lld
    ",ans);
    	}
    	return 0;
    }
    

    [NOI2010] 航空管制

    Link

    Solution

    菜肴制作有点像,都是反着来拓扑。

    把第一类限制转换为在...之后才能出现,好做一些。

    第一问就是这样。

    第二问,考虑一个数,能不选择不选,让它尽量晚出现。

    用堆的话多一个(log),要开(O2)

    Code

    #include<bits/stdc++.h>
    #define N (2010)
    #define M (20010)
    using namespace std;
    struct xbk{int ed,nx;}e[M];
    struct zyj{
    	int id,k;
    	bool operator < (const zyj &b) const{
    		return k>b.k;
    	}
    };
    int n,m,cnt,tot,lr,r[N],k[N];
    int now[N],head[N],aa[N],rd[N],ans1[N],ans2[N];
    bool used[N];
    queue<int>q;
    inline int read(){
    	int w=0;
    	char ch=getchar();
    	while(ch>'9'||ch<'0') ch=getchar();
    	while(ch>='0'&&ch<='9'){
    		w=(w<<3)+(w<<1)+(ch^48);
    		ch=getchar();
    	}
    	return w;
    }
    inline void add(int a,int b){
    	e[++tot].ed=b;
    	e[tot].nx=head[a];
    	head[a]=tot;
    }
    int main(){
    	n=read(),m=read();
    	for(int i=1;i<=n;i++) k[i]=n-read();
    	for(int i=1;i<=m;i++){
    		int u=read(),v=read();
    		add(v,u);
    		rd[u]++;
    	}
    	for(int i=1;i<=n;i++) r[i]=rd[i];
    	for(int l=0;l<=n;l++){
    		cnt=0;
          //一个缓冲堆,用来保存入度减到0但还不满足第一类限制的飞机
    		priority_queue<zyj>qq;
    		for(int i=1;i<=n;i++) rd[i]=r[i];
    		for(int i=1;i<=n;i++)
    			if(!rd[i]) qq.push((zyj){i,k[i]});	
    		while(qq.size()&&qq.top().k<=cnt){
    			q.push(qq.top().id);
    			qq.pop();
    		}	
    		while(!q.empty()){
              //能不选则不选
    			if(q.front()==l&&q.size()>=2){
    				q.pop();
    				q.push(l);
    			}
    			int nw=q.front();
    			q.pop();
    			now[++cnt]=nw;
    			if(nw==l){
    				ans2[l]=n-cnt;
    				break;
    			}
    			for(int i=head[nw];i;i=e[i].nx){
    				int ed=e[i].ed;
    				if(--rd[ed]==0) qq.push((zyj){ed,k[ed]});
    			}
    			while(qq.size()&&qq.top().k<=cnt){
    				q.push(qq.top().id);
    				qq.pop();
    			}			
    		}
    		if(l==0) for(int i=1;i<=n;i++) ans1[i]=now[i];
    	}
      //倒着输出
    	for(int i=n;i>=1;i--) printf("%d ",ans1[i]);
    	puts("");
    	for(int i=1;i<=n;i++) printf("%d ",ans2[i]+1);
    	puts("");
    	return 0;
    }
    

    CF1442D Sum

    Link

    Solution

    贪心+分治+背包

    重要结论:要选,就把一个序列选完

    没了,证明可以多想一想,假设一下就出来了

    Code

    #include<bits/stdc++.h>
    #define N (3010)
    #define ll long long
    using namespace std;
    int n,k,cnt[N];
    ll f[N],ans;
    vector<ll>v[N];
    inline ll read(){
    	ll w=0;
    	char ch=getchar();
    	while(ch>'9'||ch<'0') ch=getchar();
    	while(ch>='0'&&ch<='9'){
    		w=(w<<3)+(w<<1)+(ch^48);
    		ch=getchar();
    	}
    	return w;
    }
    inline void update(int pos){
      //全选编号为pos的序列,背包
    	for(int i=k;i>=cnt[pos];i--) f[i]=max(f[i],f[i-cnt[pos]]+v[pos][cnt[pos]]);
    }
    inline void solo(int l,int r){
    	if(l==r){
          //选到最后了,部分选择一个序列
    		for(int i=1;i<=min(k,cnt[l]);i++) f[k]=max(f[k],f[k-i]+v[l][i]);
    		ans=max(ans,f[k]);
    		return;
    	}
    	int mid=(l+r)>>1;
    	vector<ll>tmp;
      //存储原始状态
    	for(int i=0;i<=k;i++) tmp.push_back(f[i]);
    	for(int i=l;i<=mid;i++) update(i);
    	solo(mid+1,r);
      //左边的全选,拆着选的到右边去选
    	for(int i=0;i<=k;i++) f[i]=tmp[i];
    	for(int i=mid+1;i<=r;i++) update(i);
    	solo(l,mid);
      //还原后右边的全选,拆着选的到左边去选
    	for(int i=0;i<=k;i++) f[i]=tmp[i];
      //还原
      	return;
    }
    int main(){
    	n=read(),k=read();
    	for(int i=1;i<=n;i++){
    		cnt[i]=read();
    		v[i].push_back(0);
    		for(int j=1;j<=cnt[i];j++) v[i].push_back(v[i][j-1]+read());
          //v[i][j]表示在第i个序列里选j个
    	}
    	solo(1,n);
    	printf("%lld
    ",ans);
    	return 0;
    }
    

    完结撒花❀

  • 相关阅读:
    MyBatis的Mapper接口以及Example的实例函数及详解
    数据存储
    广播
    java线程复习3(线程的中断)
    java线程复习2(获取和设置线程信息)
    java线程复习1(线程创建)
    最好的启动方式
    工厂模式
    欧几里得算法
    组合数打表
  • 原文地址:https://www.cnblogs.com/xxbbkk/p/14413735.html
Copyright © 2011-2022 走看看