奶牛题题解
这三道题一定来自USACO,因为USACO就乐意出奶牛的题。
T1 奶牛晒衣服
【问题描述】
在熊大妈英明的带领下,时针和他的同伴生下了许多牛宝宝。熊大妈决定给每个宝宝都穿上可爱的婴儿装。于是,为牛宝宝洗晒衣服就成了很不爽的事情。
圣人王担负起了这个重任。洗完衣服后,你就要弄干衣服。衣服在自然条件下用 1 的时间可以晒干 A 点湿度。抠门的熊大妈买了 1 台烘衣机。使用烘衣机可以让你用 1 的时间使 1件衣服除开自然晒干的 A 点湿度外,还可烘干 B 点湿度,但在 1 的时间内只能对 1 件衣服使用。
N 件的衣服因为种种原因而不一样湿,现在告诉你每件衣服的湿度,要你求出弄干所有衣服的最少时间(湿度为 0 为干)。
【输入格式】
第一行 N , A , B ,接下来N行,每行一个数,表示衣服的湿度(1<= 湿度,A,B<=500000,1<=N<=500000)。
【输出格式】
一行,最少时间。
【输入样例】
Dry.in
3 2 1
1
2
3
【输出样例】
Dry.out
1
【样例解析】
第 1 个时间内,用机器处理第 3 件衣服,此外,所有衣服自然晒干 2。花费 1 时间全部弄干。
我的方法:
通过观察样例和题面不难发现,这是一道贪心题。我们只要想出正确的贪心策略,再模拟一下就可以了。
可行的贪心策略是:每次取湿度最大的衣服用烘干机晾干,直到所有衣服被晾干。
注意,不要每次都sort一遍,会浪费时间,要用优先队列。因为sort是将整个序列排一遍序,在algorithm头文件中,他的时间复杂度为稳定的O(nlogn),而在优先队列中,插入一个元素并把它排好序的时间复杂度的时间为O(logn),所以快很多。
另一种方法
设a[i]为第i件衣服的湿度,当前答案范围是[L,R]。(一开始答案范围是[1,500000])
先二分一个答案ans,然后枚举每件衣服,若a[i]<=ans*A,则表示第i件衣服可以自然晒干,否则表示需要用(a[i]-ans*A)/B(取上整)个单位时间的烘衣机,我们用time累计烘衣机所用的时间。
若最后time<=ans,则表示该答案可行,更新答案,并继续在[L,ans-1]内进行二分答案。
若最后time>ans,则表示该答案不行,继续在[ans+1,R]内进行二分答案。
直到L>R停止。
我的代码:
//Powered by Dev C++ 2018/3/10 08:21 #include<cstdio> #include<algorithm> #include<queue> using namespace std; int k,a,b,c,n; int flag=0; int time=0; int standard; int main() { priority_queue <int> gar; // freopen("dry.in","r",stdin); // freopen("dry.out","w",stdout); scanf("%d%d%d",&n,&a,&b); c = a + b; for(int i=1;i<=n;i++) { scanf("%d",&k); gar.push(k); } while(!gar.empty()) { k = gar.top(); if(k <= standard) break; gar.pop(); k -= b; standard += a; gar.push(k); time++; } printf("%d",time); return 0; }
T2 奶牛排队
【问题描述】
奶牛在熊大妈的带领下排成了一条直队。
显然,不同的奶牛身高不一定相同……
现在,奶牛们想知道,如果找出一些连续的奶牛,要求最左边的奶牛 A 是最矮的,最右边的 B 是最高的,且 B 高于 A 奶牛,且中间如果存在奶牛,则身高不能和 A、B 奶牛相同,问这样的一些奶牛最多会有多少头。
从左到右给出奶牛的身高,请告诉它们符合条件的最多的奶牛数(答案可能是零、二,但不会是一)。
【输入格式】
第一行一个数 N(2<=N<=100000),表示奶牛的头数。
接 下 来 N 个 数 ,每 行 一 个数 , 从 上 到下 表 示 从左 到 右 奶牛 的 身 高 (1<=身高<=maxlongint)。
【输出格式】
一行,表示最多奶牛数。
【输入样例】
Tahort.in
5
1
2
3
4
1
【输出样例】
Tahort.out
4
【样例解析】
取第 1 头到第 4 头奶牛,满足条件且为最多。
题解方法:
题外话:这道题貌似正解是单调栈……只是不知道怎么写,反正Drug是按这个方法讲给我的,但是我没听懂,不过下面的题解很易懂。
对于奶牛的高度序列从L到R,我们可以找出序列中最小高度的奶牛编号min(若有多个,取编号大的)和最大高度的奶牛编号max(若有多个,取编号小的)。那么我们最终的答案序列一定不会跨过这两个奶牛,否则就不是可行序列了。所以整个序列被我们分成了三个部分。
若min<max,则从min到max就是一个可行的序列,用(max-min+1)更新答案,并将[L,min-1]与[max+1,R]分别递归求解。如下图:
若min>max,则将[L,max],[max+1,min-1],[min,R]三部分分别进行递归求解。如下图:
那么现在的问题就是如何尽量快地找出一段序列的最小值与最大值,这是个经典的RMQ问题,使用ST算法。
用f[i][j]表示从i开始长度为2j的一段中的最小值,那么:
f[i][0] = a[i](a[i]表示原序列)
f[i][j] = min{f[i][j-1], f[i+2j-1][j-1]}
用动规便可求出f[i][j]。那么当我们要求[L,R]段的最小值时,就直接可以算出为:
min{f[L][Len], f[R-2Len+1][Len]}(其中Len=trunc(log2(R-L+1)))
f[i][j]的状态量为O(nlog2n),所以我们预处理f[i][j]的时间复杂度为O(nlog2n)。求一段中的最小值时我们就只需要O(1)的时间便可以算出。递归求序列最多有n层,所以总时间复杂度为O(nlog2n+n)。
参考代码:
//Powered by Dev C++ 2018/3/10 09:11 #include<cstdio> #include<algorithm> typedef long long ll; using namespace std; ll a[100000],b[5000000],c[5000000]; ll n,i,maximum; ll bub(ll k,ll l,ll r) { ll o,q,w; o = (l + r) / 2; if (l == r) b[k] = l; else { q = bub(k*2,l,o); w = bub(k*2+1,o+1,r); if(a[q] > a[w]) b[k] = q; else b[k] = w; } return b[k]; } ll bus(ll k,ll l,ll r) { ll o,q,w; o = (l + r) / 2; if (l == r) c[k] = l; else { q = bus(k*2,l,o); w = bus(k*2+1,o+1,r); if(a[q] < a[w]) c[k] = q; else c[k] = w; } return c[k]; } ll fb(ll k,ll l,ll r,ll x,ll y) { ll o,q,w; o = (l + r) / 2; if(x <= b[k] && y >= b[k]) return b[k]; else { if(y <= o) return fb(k*2,l,o,x,y); else if(x > o) return fb(k*2+1,o+1,r,x,y); else { q = fb(k*2,l,o,x,o); w = fb(k*2+1,o+1,r,o+1,y); if(a[q] > a[w]) return q; else return w; } } } ll fs(ll k,ll l,ll r,ll x,ll y) { ll o,q,w; o = (l + r) / 2; if(x <= c[k] && y >= c[k]) return c[k]; else { if(y <= o) return fs(k*2,l,o,x,y); else if(x > o) return fs(k*2+1,o+1,r,x,y); else { q = fs(k*2,l,o,x,o); w = fs(k*2+1,o+1,r,o+1,y); if(a[q] < a[w]) return q; else return w; } } } void work(ll l,ll r) { ll x,y; if(l >= r) return; x = fs(1,1,n,l,r); y = fb(1,1,n,l,r); if(y > x) { if(y-x > maximum) maximum = y - x; if(x-l > 1 + maximum) work(l,x-1); if(r-y > 1 + maximum) work(y+1,r); } else if(y < x) { if(y-1 > maximum) work(l,y); if(x-y > maximum + 2) work(y+1,x-1); if(r-x > maximum) work(x,r); } } int main() { // freopen("tahort.in","r",stdin); // freopen("tahort.out","w",stdout); scanf("%lld",&n); maximum = 1; for(i=1;i<=n;i++) { scanf("%lld",&a[i]); } bub(1,1,n); bus(1,1,n); if(n > 1) work(1,n); printf("%lld",maximum+1); return 0; }
T3 圆圈舞蹈
【问题描述】
熊大妈的奶牛在时针的带领下,围成了一个圈跳舞。由于没有严格的教育,奶牛们之间的间隔不一致。奶牛想知道两只最远的奶牛到底隔了多远。奶牛 A 到 B 的距离为 A 顺时针走和逆时针走,到达 B 的较短路程。告诉你相邻个奶牛间的距离,请你告诉奶牛两只最远的奶牛到底隔了多远。
【输入格式】
第一行一个整数 N,表示有 N 只奶牛。(2<=N<=100000)
接下来 2~N+1 行,第 I 行有一个数,表示第 I-1 头奶牛顺时针到第 I 头奶牛的距离。(1<= 距离<=maxlongint,距离和<=maxlongint)
第 N+1 行的数表示第 N 头奶牛顺时针到第 1 头奶牛的距离
【输出格式】
一行,表示最大距离。
【输入样例】
Circle.in
5
1
2
3
4
5
【输出样例】
Circle.out
7
解析:
这道题需要算法优化。
最笨的方法:多源最短路Floyd,或者从每头牛开始枚举与其他牛的距离,再枚举找到最大距离。时间复杂度是o(n^3),显然不合适。然而我过了四个点(划死。
正确解法:其实我们并不需要知道所有奶牛之间的距离,我们只需要知道对于每头奶牛,离它最远的奶牛的距离是多少。所以首先我们枚举一头奶牛,然后对于这头奶牛求一个最远的奶牛即可(注意:我们应该需要求出两头最远的奶牛,一头顺时针距离最远的,一头逆时针距离最远的)。
对于枚举的第一头奶牛A,我们沿着圈找到一头离它最远的奶牛B。
当我们沿着顺时针方向枚举第二头奶牛C时,离C最远的奶牛就不可能是图中红色区域的奶牛了,所以我们只需将B向顺时针方向枚举,当蓝色部分的距离小于红色部分时枚举停止,因为此时蓝色部分的奶牛就不可能是离C最远的奶牛了。
对于这种方法,我们枚举的两个指针分别绕环转了一圈,所以时间复杂度为O(n),可以过题目所给的数据范围了。
参考代码:
//Powered by Dev C++ 2018/3/10 09:43 //Be corrected by 2018/3/10 15:30 //CCCP:Only the dead bear is the good bear. #include<cstdio> #include<algorithm> using namespace std; typedef long long ll; ll shun[100005]; ll nai[100005]; ll ans[100005]; int main() { int n; int flag; ll quan = 0; ll maximum = 0; // freopen("circle.in","r",stdin); // freopen("circle.out","w",stdout); scanf("%d",&n); for(int i=1;i<=n;i++) { scanf("%lld",&nai[i]); shun[i] = shun[i-1] + nai[i]; quan += nai[i]; } flag = 1; for(int i=1;i<=n;i++) { for(int j=flag-1;j<=n;j++) { if( min(shun[j+1] - shun[i],quan - (shun[j+1] - shun[i]) ) < min( shun[j] - shun[i],quan - (shun[j] - shun[i]) )) { flag = j; ans[i] = min( shun[j] - shun[i],quan - (shun[j] - shun[i]) ); break; } } } for(int i=1;i<=n;i++) { maximum = max(maximum,ans[i]); } printf("%lld",maximum); return 0; }
posted on 2018-03-11 00:33 Ch_someone 阅读(467) 评论(0) 编辑 收藏 举报