写在前面
首先衷心地感谢(psj)学长能够在百忙之中抽出时间为我们的省选讲课%%%(虽然蒟蒻还没有省选的水平),感谢!!!
贪心是基础算法,应用广泛,结合性强,可以与许多算法综合考察。一般来说,有了贪心,一道题的思维难度上去了。这几天做的贪心,不同于以往的纯思维,而是思维+码力,相对来说综合性强一些。
一些题目。
CF545C Woodcutters
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
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 加法
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] 航空管制
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
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;
}