练贪心!贪心!贪心!

今天模拟赛好水的一道贪心还想了半天,所以我要练!练!练!
bzoj 1052 覆盖
用三个等大的正方形覆盖所有点,使正方形边长最小
二分边长,贪心判定:用一个最小的矩形覆盖所有点,则第一个正方形的一个顶点一定是矩形的一个顶点,第二个正方形在剩下的点中这个性质依然成立。

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>

#define ll long long
#define inf 1e9
#define eps 1e-10
#define md
#define N 20010
using namespace std;
struct point { int x,y;} p[N];
int vis[N];
int n,A,B,C,D;
bool jd(int x,int y,int len)
{
//printf(" jd: %d %d %d\n",x,y,len);
int A=inf,B=-inf,C=-inf,D=inf;
for (int i=1;i<=n;i++)
{
if (vis[i]==1) continue;
if (x<=p[i].x&&p[i].x<=x+len&&y<=p[i].y&&p[i].y<=y+len) vis[i]=2;
else
{
A=min(A,p[i].x); B=max(B,p[i].x);
C=max(C,p[i].y); D=min(D,p[i].y);
}
}
//for (int i=1;i<=n;i++) printf("%d ",vis[i]); printf("\n");
//printf(" abcd: %d %d %d %d\n",A,B,C,D);
if (A==inf||(B-A<=len&&C-D<=len)) return 1; else return 0;
}

bool solve(int x,int y,int len)
{
//printf("solve: %d %d %d\n",x,y,len);
memset(vis,0,sizeof(vis));
int A=inf,B=-inf,C=-inf,D=inf;
for (int i=1;i<=n;i++)
{
if (x<=p[i].x&&p[i].x<=x+len&&y<=p[i].y&&p[i].y<=y+len) vis[i]=1;
else
{
A=min(A,p[i].x); B=max(B,p[i].x);
C=max(C,p[i].y); D=min(D,p[i].y);
}
}
if (A==inf) return 1;
if (jd(A,D,len)) return 1;
if (jd(A,C-len,len)) return 1;
if (jd(B-len,D,len)) return 1;
if (jd(B-len,C-len,len)) return 1;
return 0;
}

bool ok(int len)
{
if (solve(A,D,len)) return 1;
if (solve(A,C-len,len)) return 1;
if (solve(B-len,D,len)) return 1;
if (solve(B-len,C-len,len)) return 1;
return 0;
}
int main()
{
scanf("%d",&n);
A=inf; B=-inf; C=-inf; D=inf;
for (int i=1;i<=n;i++)
{
scanf("%d%d",&p[i].x,&p[i].y);
A=min(A,p[i].x); B=max(B,p[i].x);
C=max(C,p[i].y); D=min(D,p[i].y);
}
int l=1,r=inf;
while (l!=r)
{
int mid=(l+r)>>1;
if (ok(mid)) r=mid; else l=mid+1;
}
printf("%d\n",l);
return 0;
}

bzoj 2811 覆盖
一个01序列,要求有的区间必须全是0,有的区间必须有1,询问哪些点一定是1.
首先将必须是0的区间删掉,如果这时只剩k个点,直接输出。
通过单调栈留下一些互不包含的区间。
要使放的1最少,一定是依次枚举每个区间,如果当前区间内没有1,就在区间的右端点放上1.然后明显其它的点都是可放可不放的。
用f[i],g[i]分别表示如此做从左往右/从右往左前i个区间放了多少个1.
枚举每一个右端点放1的区间,如果将这个1左移一位之后需要的点数大于k,那么这个点一定要放1,否则不就可以放在左移一位的位置上了吗?
那么如何判断是否大于k?
在i之前的最右面的不覆盖i-1的区间t1的f值加上在i之后最左面的不覆盖i-1的区间t2的g值,再加上i-1的位置放上的1,这样所有的区间都满足要求。也就是如果f[t1]+g[t2]+1>k,就输出第i个区间的右端点。
注意寻找包含区间是最左面的q[i].r>=pos 和最右边的 q[i].l<=pos。

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>

#define ll long long
#define inf 1e9
#define eps 1e-10
#define md
#define N 100010
using namespace std;
struct data { int l,r,c;} q[N],a[N];
int c[N],is[N],ql[N],qr[N],f[N],g[N];
int n;
bool cmp(data a,data b) { return a.l==b.l?a.r>b.r:a.l<b.l;}
void add(int x,int d)
{
for (;x<=n;x+=x&(-x)) c[x]+=d;
}
int qsum(int x)
{
int ans=0;
for (;x;x-=x&(-x)) ans+=c[x];
return ans;
}
int main()
{
int K,m;
scanf("%d%d%d",&n,&K,&m);
for (int i=1;i<=m;i++)
{
scanf("%d%d%d",&a[i].l,&a[i].r,&a[i].c);
if (a[i].c==0)
{
add(a[i].l,1); add(a[i].r+1,-1);
}
}
int w=0;
for (int i=1;i<=n;i++)
{
if (qsum(i)==0)
{
w++; is[w]=i; ql[i]=w; qr[i]=w;
}
}
if (w==K) { for (int i=1;i<=K;i++) printf("%d\n",is[i]); return 0;}
ql[n+1]=inf; for (int i=n;i;i--) if (!ql[i]) ql[i]=ql[i+1];
for (int i=1;i<=n;i++) if (!qr[i]) qr[i]=qr[i-1];
//for (int i=1;i<=n;i++) printf("%d %d ",ql[i],qr[i]); printf("\n");
int top=0;
for (int i=1;i<=m;i++) { a[i].l=ql[a[i].l]; a[i].r=qr[a[i].r];}
//for (int i=1;i<=m;i++) printf("%d %d %d\n",a[i].l,a[i].r,a[i].c); printf("\n");
sort(a+1,a+m+1,cmp);
for (int i=1;i<=m;i++)
{
if (a[i].c)
{
if (a[i].l>a[i].r) continue;
while (top&&q[top].l<=a[i].l&&a[i].r<=q[top].r) top--;
q[++top]=a[i];
}
}
//for (int i=1;i<=top;i++) printf("%d %d\n",q[i].l,q[i].r);
//q[top+1].l=q[top+1].r=inf;
int mn=0,mx=inf;
for (int i=1;i<=top;i++)
{
if (mn<q[i].l) { mn=q[i].r; f[i]=f[i-1]+1;}
else f[i]=f[i-1];
}
for (int i=top;i;i--)
{
if (mx>q[i].r) { mx=q[i].l; g[i]=g[i+1]+1;}
else g[i]=g[i+1];
}
//for (int i=1;i<=top;i++) printf("%d ",f[i]); printf("\n");
//for (int i=1;i<=top;i++) printf("%d ",g[i]); printf("\n");
bool hv=0;
for (int i=1;i<=top;i++)
{
if (q[i].l==q[i].r)
{
printf("%d\n",is[q[i].l]);
hv=1;
continue;
}
if (f[i]==f[i-1]) continue;
else
{
int pos=q[i].r-1;
int l=0,r=top+1;
while (l!=r)
{
int mid=(l+r+1)>>1;
if (q[mid].r<pos) l=mid; else r=mid-1;
}
int t1=l;
l=t1; r=top+1;
while (l!=r)
{
int mid=(l+r)>>1;
if (q[mid].l>pos) r=mid; else l=mid+1;
}
int t2=l;
if (f[t1]+g[t2]+1>K)
{
printf("%d\n",is[q[i].r]);
hv=1;
}
}
}
if (!hv) printf("-1\n");
return 0;
}

bzoj 1367 sequence
练贪心!贪心!贪心! - zc - zc的博客
首先对于递增序列,a[i]减去i,然后就变成不下降序列。这样z数组就是几段单调递增的连续的数,就像1122234455这样。然后如果第i段数的中位数小于第i-1段数,那么就合并这两个区间,然后这个区间的答案变成这个区间的中位数。这个操作可以用可并堆维护。 因为第i个区间的中位数单调不降,所以只需要维护最大的size/2个数

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>

#define ll long long
#define inf 1e9
#define eps 1e-10
#define md
#define N 1000010
using namespace std;
int a[N],v[N],lx[N],rx[N],sz[N],dis[N],root[N],L[N],R[N];
struct left_tree
{
int top(int x)
{
return v[x];
}
int merge(int x,int y)
{
if (!x) return y;
if (!y) return x;
if (v[x]<v[y]) swap(x,y);
rx[x]=merge(rx[x],y);
sz[x]=sz[lx[x]]+sz[rx[x]]+1;
if (dis[rx[x]]>dis[lx[x]]) swap(lx[x],rx[x]);
dis[x]=dis[rx[x]]+1;
return x;
}
} heap;

int main()
{
int n;
scanf("%d",&n);
for (int i=1;i<=n;i++) { scanf("%d",&a[i]); a[i]-=i; }
int top=0;
for (int i=1;i<=n;i++)
{
sz[i]=dis[i]=1; lx[i]=rx[i]=0; v[i]=a[i];
root[++top]=i; L[top]=R[top]=i;
while (top>1&&heap.top(root[top-1])>heap.top(root[top]))
{
top--;
root[top]=heap.merge(root[top],root[top+1]);
R[top]=R[top+1];
int tot=R[top]-L[top]+1;
while (sz[root[top]]*2>tot+1) root[top]=heap.merge(lx[root[top]],rx[root[top]]);
}
//for (int j=1;j<=top;j++) printf("L: %d R: %d mid: %d\n",L[j],R[j],heap.top(root[j])); printf("\n");
}
ll ans=0;
for (int i=1;i<=top;i++)
{
int mid=heap.top(root[i]); //printf("\nL:%d R:%d mid: %d\n",L[i],R[i],mid);
for (int j=L[i];j<=R[i];j++) ans+=abs(a[j]-mid); //printf("%d %d ",a[j],mid);
}//printf("\n");
printf("%lld\n",ans);
return 0;
}

bzoj 2288 礼物
从一个序列中选出至少m个区间,使他们的和最大
表示看到这道题的第一反应是那道被讲了好多遍的用线段树维护增广路
首先将连续的正数合并,连续的负数合并,答案等于所有正数的和。
维护一个堆,每次找出绝对值最小的区间,答案减去这个绝对值,然后将这个区间,它左面的区间,它右面的区间合并。这样子,堆中的元素就是选与不选交错,正数与负数交错(因为合并前两边数的绝对值大于这个数)。
选择一个负数,相当于选上这段区间,选择一个正数,相当于不选这个区间,这都能使区间数减一。
考虑边界,第一个和最后一个区间出堆之后,如果是正数,就去掉这个区间,答案减去。如果是负数,不但不会减少区间数,还会使答案变小,所以直接忽略
删除任意一个数,不想手写堆,写了set
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<set>
#define ll long long
#define inf 1e9
#define eps 1e-10
#define md
#define N 100010
using namespace std;
int a[N],q[N],pre[N],ne[N];
struct data { int sm,pos,ab; };
struct cmp
{
bool operator () (data a,data b)
{
return a.ab==b.ab? a.pos<b.pos:a.ab<b.ab;
}
};
set<data,cmp> st;
int main()
{
int n,m;
scanf("%d%d",&n,&m);
int w=0;
for (int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
if (a[i]==0) { i--; n--; continue; }
if (a[i]*a[i-1]>0) q[w]+=a[i];
else q[++w]=a[i];
}
n=w; int ans=0,tot=0;
memset(a,0,sizeof(a));
for (int i=1;i<=n;i++)
{
a[i]=q[i];
if (a[i]>0) { ans+=a[i]; tot++; }
pre[i]=i-1; ne[i]=i+1;
data x=(data) { a[i],i,abs(a[i])}; st.insert(x); //printf("insert: %d\n",x.ab);
}
//for (set<data,cmp>::iterator i=st.begin();i!=st.end();i++) printf("%d ",(*i).ab); printf("\n");
if (tot<=m)
{
printf("%d\n",ans);
return 0;
}
while (tot>m)
{
data x=*st.begin();
int pos=x.pos,sm=x.sm; //printf("erase: %d %d\n",pos,sm);
int L=pre[pos],R=ne[pos];
if (L<1)
{
pre[R]=pre[pos];
if (sm>0) { tot--; ans-=x.ab; st.erase(x); }
else { st.erase(x); }
}
else if (R>n)
{
ne[L]=ne[pos];
if (sm>0) { tot--; ans-=x.ab; st.erase(x); }
else { st.erase(x); }
}
else
{
ans-=x.ab;
a[pos]=a[L]+a[R]+a[pos];
st.erase(x);
data y=(data) {a[L],L,abs(a[L])}; st.erase(y);
y=(data) {a[R],R,abs(a[R])}; st.erase(y);
x.sm=a[pos]; x.ab=abs(a[pos]); st.insert(x);
pre[pos]=pre[L]; ne[pre[L]]=pos;
ne[pos]=ne[R]; pre[ne[R]]=pos;
tot--;
}
}
printf("%d\n",ans);
return 0;
}
bzoj 4198 荷马史诗
这是一道裸的k叉哈夫曼树,每次找到权值最小的k个数合并成一个新节点,答案加上这些权值和。
要求深度最小,只需要让深度最小的先出堆。
然后将点数补成(k-1)*d+1

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<queue>
#define ll long long
#define inf 1e9
#define eps 1e-10
#define md
#define N 100500
using namespace std;
struct data { ll dt; int dep; } a[N];
struct cmp
{
bool operator () (data a,data b) { return a.dt==b.dt?a.dep>b.dep:a.dt>b.dt;}
};
priority_queue<data,vector<data>,cmp> q;
int main()
{
int n,k;
scanf("%d%d",&n,&k);
for (int i=1;i<=n;i++) { scanf("%lld",&a[i].dt); a[i].dep=1;}
while (k!=2&&(n%(k-1))!=1) { n++; a[n].dep=a[n].dt=0; }
for (int i=1;i<=n;i++) q.push(a[i]);
ll ans=0;
while (q.size()!=1)
{
ll sum=0; int mxdep=0;
data x;
for (int i=1;i<=k;i++)
{
x=q.top(); q.pop(); //printf("pop: %lld %d\n",x.dt,x.dep);
sum+=x.dt; mxdep=max(mxdep,x.dep);
}
x.dt=sum; x.dep=mxdep+1; ans+=sum;
q.push(x);
}
printf("%lld\n%d\n",ans,(q.top()).dep-1);
return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值