贪心小结

写在前面

首先衷心地感谢(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;
}

完结撒花❀

原文地址:https://www.cnblogs.com/xxbbkk/p/14413735.html