庙会捷运Fair Shuttle
虽然,农民伯伯JOHN可以轻松地在庙会上游览,拿拿奖品或看看节目,可是他的奶牛们非常缺乏锻炼,游览完一整
天的庙会,他们已经筋疲力尽。为了让奶牛们也能开心地游览庙会,农民伯伯JOHN要自己选择捷运卡车线路,让他
们的奶牛可以不费吹灰之力在庙会的不同展台穿梭自由。 可是,农民伯伯JOHN木有(没有)米(钱),他可付不
起豪华线路的钱,所以他选的捷运只能停N(1<= N<= 20,000) 个展台,而且某个线路不能有丝毫重复。奶牛们自
主分为了 K(1<=K<=50,000)组,被编号为1..K。第i组的奶牛中有M_i(1<=M_i<=N)头想从展台 S_i(1<=S_i<=N). 捷
运由于容量有限,也许不能载下所有想乘坐的奶牛们。组里一部分的奶牛就得单独乘坐咯。 现在,农民伯伯JOHN
经过调查得知,每个捷运卡车的容量是C(1<=C<=100),和每个组的奶牛在庙会里想去参观的不同展台,请你帮农民
伯伯决定最多能有多少头牛可以在庙会上乘坐舒适的捷运卡车。
输入
第1行: 三个整数: K,N,C。 由空格隔开。
第2..K+1行:第i+1行,告诉你第i组奶牛的信息: S_i, E_i and M_i。由空格隔开。
输出
一行:可以在庙会乘坐捷运的牛的最大头数
样例输入 Copy
8 15 3
1 5 2
13 14 1
5 8 3
8 14 2
14 15 1
9 12 1
12 15 2
4 6 1
10
捷运可以把
2头奶牛从展台1送到展台5,
3头奶牛从展台5到展台8,
2头奶牛从展台8到展台14,
1头奶牛从展台9送到展台12,
1头奶牛从展台13送到展台14,
1头奶牛从14送到15。
Sol
我们来考虑一种贪心的思想,按右端点从小到大排一遍序,为什么呢?后面说,然后对排好序的每堆奶牛依次进行遍历,如果当前有空位,空位大于这堆奶牛的数量,那就全上去,不然的话,就能上去几个就上去几个。这样下来的话,结果一定是最优的,其正确性不难证明,因为刚开始我们对每堆奶牛要到的地方从小到大排了序(即终点),那么每个位置最多只有一次奶牛上车,而且这些奶牛来自同一群,所以我们对每堆奶牛分别进行考虑即可,这就是为什么要按右端点排序的原因。贪心过程中,要维护最大值。因为要算最少的空位子,下面给出两种代码:
#include<cstdio> #include<algorithm> #include<cctype> #define maxn 50007 using namespace std; int ans,n,m,k,w[maxn]; inline int qread() { char c=getchar();int num=0,f=1; for(;!isdigit(c);c=getchar()) if(c=='-') f=-1; for(;isdigit(c);c=getchar()) num=num*10+c-'0'; return num*f; } struct node { int u,v,c; }e[maxn]; const int inf=0x7fffffff; inline bool cmp(node a, node b) { if(a.v!=b.v) return a.v<b.v; return a.u<b.u; } int main() { k=qread(),n=qread(),m=qread(); for(int i=1;i<=k;++i) e[i].u=qread(),e[i].v=qread(),e[i].c=qread(); sort(e+1,e+1+k,cmp); for(int i=1;i<=k;++i) { if(w[e[i].u]>=m) //在这个时刻车上已有多于M个人了 continue; int minn=inf; for(int j=e[i].u;j<=e[i].v;++j) //统计这个时间段,可以上多少人 { minn=min(m-w[j],minn); if(minn<=0) break; } if(minn>0) //如果可以上人的话 { if(minn>=e[i].c) //如果可以全部上 { for(int j=e[i].u;j<e[i].v;++j) w[j]+=e[i].c; ans+=e[i].c; } else //只能上一部分 { for(int j=e[i].u;j<e[i].v;++j) w[j]+=minn; ans+=minn; } } } printf("%d\n",ans); return 0; }
线段树:
#include<cstdio> #include<algorithm> #include<cctype> #define maxn 20007 #define ls rt<<1 #define rs rt<<1|1 using namespace std; const int inf=0x7fffffff; int n,k,m,maxx[maxn<<2],lazy[maxn<<2],zrj,w[50007]; inline int qread() { char c=getchar();int num=0,f=1; for(;!isdigit(c);c=getchar()) if(c=='-') f=-1; for(;isdigit(c);c=getchar()) num=num*10+c-'0'; return num*f; } struct node { int u,v,c; }e[50007]; inline bool cmp(node a, node b) { if(a.v!=b.v) return a.v<b.v; return a.u<b.u; } inline void pushup(int rt) { maxx[rt]=max(maxx[ls],maxx[rs]); } inline void pushdown(int rt) { if(lazy[rt]) { maxx[ls]+=lazy[rt],lazy[ls]+=lazy[rt]; maxx[rs]+=lazy[rt],lazy[rs]+=lazy[rt]; lazy[rt]=0; } } void modify(int rt, int l, int r, int L, int R, int val) { if(L>r||R<l) return; if(L<=l&&r<=R) { lazy[rt]+=val,maxx[rt]+=val; return; } int mid=(l+r)>>1; pushdown(rt); modify(ls,l,mid,L,R,val),modify(rs,mid+1,r,L,R,val); pushup(rt); } int cmax(int rt, int l, int r, int L, int R) { if(L>r||R<l) return -inf; if(L<=l&&r<=R) return maxx[rt]; int mid=(l+r)>>1,ans=-inf; pushdown(rt); if(L<=mid) ans=max(ans,cmax(ls,l,mid,L,R)); if(R>mid) ans=max(ans,cmax(rs,mid+1,r,L,R)); return ans; } int main() { k=qread(),n=qread(),m=qread(); for(int i=1;i<=k;++i) e[i].u=qread(),e[i].v=qread(),e[i].c=qread(); sort(e+1,e+1+k,cmp); for(int i=1;i<=k;++i) { int p=cmax(1,1,n,e[i].u,e[i].v-1); int minn=min(m-p,e[i].c); zrj+=minn,w[i]+=minn; modify(1,1,n,e[i].u,e[i].v-1,w[i]); } printf("%d\n",zrj); return 0; }
Sol:
对于此题,易知我们更喜欢那些到站点小一些的牛,于是按结束位置从小到大进行排序。
那么每头牛如何选择座位呢?
如下图如示
按照排序后的顺序,奶牛1会排在前面。如果奶牛1抢了座位1,那么奶牛2就坐不进去了。
而奶牛1的时间实际上是可以坐进座位2的,这样给空位更大的座位1留下了创造更大价值的机会。
由此可以知道,奶牛要去接在结束时间最早且在自己开始之前结束的那个座位上。
因为座位数比较小,所以可以每次都排序,
否则可需要借助线段树来降低复杂度
下图中seat1~seat3为3个座位释放出来的时间,sum代表输运了多少头牛
#include<bits/stdc++.h> using namespace std; struct act { int s,t,num; friend bool operator <(act a,act b) { return a.t<b.t; } }a[50010]; int End[111]; bool cmp(int x,int y) { return x>y; } int main() { int k,n,c; scanf("%d%d%d",&k,&n,&c); for(int i=1;i<=k;++i) scanf("%d%d%d",&a[i].s,&a[i].t,&a[i].num); std::sort(a+1,a+1+k); int sum=0; for(int i=1;i<=k;++i)//考虑每个批次的牛 { std::sort(End+1,End+1+c,cmp);//降序排列 for(int j=1;a[i].num&&j<=c;++j) //对于每个座位,如果它的释放时间小于当前牛上车的时候 if(End[j]<=a[i].s) //优先使用早释放出来的座位 { ++sum; End[j]=a[i].t; //更新这个座位的释放时间 a[i].num--; } } printf("%d\n",sum); return 0; }
线段树维护
#include<cmath> #include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #define inf 0x7f7f7f typedef long long ll; typedef unsigned int ui; typedef unsigned long long ull; using namespace std; inline int read(){ int x=0,f=1;char ch=getchar(); for (;ch<'0'||ch>'9';ch=getchar()) if (ch=='-') f=-1; for (;ch>='0'&&ch<='9';ch=getchar()) x=(x<<3)+(x<<1)+ch-'0'; return x*f; } inline void print(int x){ if (x>=10) print(x/10); putchar(x%10+'0'); } const int N=5e4,M=2e4; int n,m,K; int root=1; struct AC{ int l,r,val; void join(int x,int y,int z){l=x,r=y,val=z;} bool operator <(const AC &x)const { return r!=x.r?r<x.r:l>x.l; } }A[N+10]; struct Tree { #define ls (p<<1) #define rs ((p<<1)|1) int Min[M*16+10],lazy[M*16+10]; void updata(int p){Min[p]=min(Min[ls],Min[rs]);} void pushdown(int p) {//“懒惰”标记 if (!lazy[p]) return; lazy[ls]+=lazy[p]; lazy[rs]+=lazy[p]; Min[ls]+=lazy[p]; Min[rs]+=lazy[p]; lazy[p]=0; } void build(int p,int l,int r) { if (l==r) { Min[p]=K; return; } //开始空位为K int mid=(l+r)>>1; build(ls,l,mid),build(rs,mid+1,r); updata(p); } int get(int p,int l,int r,int x,int y) { pushdown(p); if (x<=l&&r<=y) return Min[p]; int mid=(l+r)>>1,ans1=inf,ans2=inf; if (x<=mid) ans1=get(ls,l,mid,x,y); if (y>mid) ans2=get(rs,mid+1,r,x,y); if (ans1==inf&&ans2==inf) return 0; return min(ans1,ans2); } void change(int p,int l,int r,int x,int y,int t) { pushdown(p); if (x<=l&&r<=y) { Min[p]+=t, lazy[p]+=t; return; } int mid=(l+r)>>1; if (x<=mid) change(ls,l,mid,x,y,t); if (y>mid) change(rs,mid+1,r,x,y,t); updata(p); } }T; int main(){ n=read(),m=read(),K=read(); int ans=0; for (int i=1,x,y,z;i<=n;i++) x=read(),y=read()-1,z=read(),A[i].join(x,y,z); //因为在时刻r奶牛已经下车了,所以右端点要-- sort(A+1,A+1+n); T.build(1,1,m); for (int i=1;i<=n;i++) { int l=A[i].l,r=A[i].r; int tmp=min(A[i].val,T.get(1,1,m,l,r)); //看看能上多少奶牛,上不了的就干脆别上了 if (tmp) T.change(1,1,m,l,r,-tmp),ans+=tmp; //更新,包括答案的更新和线段树的更新 } printf("%d\n",ans); return 0; }
这道题我们维护两个堆 按终点距离为键值 维护一个大根堆 一个小根堆
我们每次到一个点 就把所有到这个点(作为终点)的奶牛扔出堆(大根堆小根堆的信息要互通)
表示他们已经下车了 到一个点之后把所有在这个点上车的牛全部扔进堆 表示他们要上车辣QAQ
如果这个时候车上的人太多了(会出事的2333) 我们就贪心地把终点最远的扔出堆
这样就可以得到最优解了2333
https://www.cnblogs.com/lyzuikeai/p/7575850.html
#include<cstdio> #include<cstring> #include<algorithm> #include<vector> #include<queue> const int M=3e4+7; int read(){ int ans=0,f=1,c=getchar(); while(c<'0'||c>'9'){if(c=='-') f=-1; c=getchar();} while(c>='0'&&c<='9') {ans=ans*10+(c-'0'); c=getchar();} return ans*f; } int f[2*M],cnt,cost,k,n,c,ans; struct pos { int id,h,ed; bool operator <(const pos &x) const{return x.ed<ed;} }; std::vector<pos>e[M]; std::priority_queue<pos>q1; struct Q { int id,h,ed; bool operator <(const Q &x) const{return x.ed>ed;} }; std::priority_queue<Q>q2; int main() { int v,x,y; k=read(); //k个运输 n=read(); //n个站点 c=read(); //c个位子 for(int i=1;i<=k;i++) x=read(),y=read(),v=read(), e[x].push_back((pos){++cnt,v,y}); for(int i=1;i<=n;i++) { while(!q1.empty()) { pos x=q1.top(); if(x.ed>i) break; q1.pop(); if(f[x.id]) continue; f[x.id]=1; cost-=x.h; ans+=x.h; } int mx=e[i].size(); for(int j=0;j<mx;j++) { q1.push((pos){e[i][j].id,e[i][j].h,e[i][j].ed}); q2.push((Q){e[i][j].id,e[i][j].h,e[i][j].ed}); cost+=e[i][j].h; } while(cost>c) { Q x=q2.top(); q2.pop(); if(f[x.id]) continue; f[x.id]=1; if(cost-c>=x.h) { cost-=x.h; continue; } int now=cost-c; cnt++; q1.push((pos){cnt,x.h-now,x.ed}); q2.push((Q){cnt,x.h-now,x.ed}); cost=c; } } printf("%d\n",ans); return 0; }