链接:登录—专业IT笔试面试备考平台_牛客网
来源:牛客网[CQOI2010]扑克牌
你有n种牌,第i种牌的数目为ci。另外有一种特殊的牌:joker,它的数目是m。你可以用每种牌各一张来组成一套牌,也可以用一张joker和除了某一种牌以外的其他牌各一张组成1套牌。比如,当n=3时,一共有4种合法的套牌:{1,2,3}, {J,2,3}, {1,J,3}, {1,2,J}。 给出n, m和ci,你的任务是组成尽量多的套牌。每张牌最多只能用在一副套牌里(可以有牌不使用)。
思路:依然是二分寻找答案,关键是判断给出的答案是否满足条件。我们可以通过枚举最多可组成牌的套数,再计算joker是否够用;还有一点,joker不能在一套牌中出现两次,这就是说最终牌的套数需要大于或等于用上的joker数。
接下来就是讨论到底能组成几套牌的问题。不得不说,想出不办法时,手动模拟各种类型(变着花样举例)真是最好的办法。我们假设有6张joker,第一张牌a[1]有3张,第二张牌a[2]4张,其他略去。用j表示joker,x表示正常牌,则不妨看以下例子:
第一张牌:j j j x x x
第二张牌:x x x j j x
其他……
按照这种对齐的方式,我们可以将牌配对,要求每一列必须只有一个joker,所需joker总数即是枚举套牌数答案mid-a[i]。要是多加了怎么办?又由鸽笼原理,我们惊喜地发现只要最终答案套牌数小于等于joker数即可。当然,还要小于等于已给定joker数。
#include<iostream>
#include<cstdio>
#include<algorithm>
//scanf("%d",&);
using namespace std;
const int maxn=300005,inf=0x7fffffff;
const double esp=1e-8;
int n,m,j,l,r,c[maxn];
int jg(int x){
long long sum=0;
for(int i=1;i<=n;++i)
if(c[i]<x) sum+=x-c[i];
return sum<=x && sum<=j;
}
int main(){
cin>>n>>j;
for(int i=1;i<=n;++i){
scanf("%d",&c[i]);
}
r=1e9;
while(l<=r){
int mid=(l+r)>>1;
if(jg(mid)) l=mid+1;
else r=mid-1;
}
cout<<l-1;
return 0;
}
链接:登录—专业IT笔试面试备考平台_牛客网
来源:牛客网
[NOIP2012]借教室面对海量租借教室的信息,我们自然希望编程解决这个问题。
我们需要处理接下来n天的借教室信息,其中第i天学校有ri个教室可供租借。共有m份订单,每份订单用三个正整数描述,分别为dj, sj, tj,表示某租借者需要从第sj天到第tj天租借教室(包括第sj天和第tj天),每天需要租借dj个教室。
借教室的原则是先到先得,也就是说我们要按照订单的先后顺序依次为每份订单分配教室。如果在分配的过程中遇到一份订单无法完全满足,则需要停止教室的分配,通知当前申请人修改订单。这里的无法满足指从第sj天到第tj天中有至少一天剩余的教室数量不足dj个。
现在我们需要知道,是否会有订单无法完全满足。如果有,需要通知哪一个申请人修改订单。
线段树的板子虽然忘了,但这道题用二分一样可以做。我们只需要不断二分得到最大天数,然后判断是否符合要求即可。具体判断方法:一眼望去就是区间加减数,这也是为啥可用线段树维护的原因,但不难想到可以通过用前缀和数组维护,只需要最后和每天可借的教室数量作比较即可。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
#include<algorithm>
#include<cmath>
using namespace std;
const int maxn=1000005, inf=0x7fffffff;
int n,m,ri[maxn],l,r;
long long dt[maxn];
struct node{
int d,s,t;
}a[maxn];
int jg(int x){
int sum=0,ed=0;
memset(dt,0,sizeof(dt));
for(int i=1;i<=x;++i){
dt[a[i].s]-=a[i].d;
dt[a[i].t+1]+=a[i].d;
ed=max(ed,a[i].t);
}
for(int i=1;i<=ed+1;++i){
sum+=dt[i];
if(sum+ri[i]<0) return 1;
}
return 0;
}
int main(){
scanf("%d%d",&n,&m);
l=1; r=n;
for(int i=1;i<=n;++i)
scanf("%d",&ri[i]);
for(int i=1;i<=m;++i)
scanf("%d%d%d",&a[i].d,&a[i].s,&a[i].t);
while(l<=r){
int mid=(l+r)>>1;
if(jg(mid)) r=mid-1;
else l=mid+1;
}
if(l-1==n) {
cout<<0;
return 0;
}
cout<<-1<<endl<<r+1;
return 0;
}
链接:登录—专业IT笔试面试备考平台_牛客网
来源:牛客网第 K 个号码
Alice 得到一个包含 N 个数字的数组 A[1..N]。
现在 Alice 想通过一个参数 K 构建一个数组 B,规则如下:
最初,数组 B 是空的。考虑数组 A 中的每个区间。如果此区间的长度小于 K,则忽略此区间。否则,在这个区间中找到第 K 个最大的数,并将这个数添加到数组 B
中。实际上 Alice 并不关心数组 B 中的每个元素。她只想知道数组 B 中的第 M 个最大元素。请帮她找到这个号码。
考虑用二分进行优化。通过枚举第m个最大元素的值x,来寻找符合条件的区间,很明显应该有m-1个区间中的第k个最大的数要大于x。这里可以通过尺取来优化(刚查了查尺取,理解到其时间复杂度为O(n)的原因是头指针不再返回QAQ)。至于为什么:
假设k=3,M=2,数组为4 3 2 5,x为2,尺取的head指向2,tail指向4。首先看前三个数,第k小数为2,不符合条件,head向后移动,指向5.这时第k小数变为3,符合条件的区间数+1.而此时发现无论如何之后的第k小数都不会小于x=2了,从而保证了head不用像二重循环一样还要回来一趟。
至于为啥二分得到的值一定存在于队列之中,这种东西自己去举一些例子就好。我们来关注二分指针,如果答案是6,是否有可能指针在指向7时被判合理呢?答案是否定的,因为与之前假设的6矛盾(满足条件区间计数需要a[i]<=x才加1),也就是说7一定会使指针向左移;同理,若指向5也肯定会向右移。这样就保证了能够指到区间中确定的值。QAQ花了好久弄明白
另外,不要忘了第m个最大的值有蛊!因为有可能这个m贼大,超出了int的范围(我吐了QAQ)
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<queue>
#include<map>
//scanf("%d",&);
using namespace std;
typedef long long ll;
const int maxn=100005,inf=0x3f3f3f3f;//inf设大了
const double esp=1e-8;
ll t,m;
int a[maxn],n,k;
int jg(ll x) {
//x为二分估计的答案值
ll cnt = 0;//第K大的数大于等于x的区间个数
ll num = 0;//当前尺取的区间中大于等于x的数的个数
for (ll left = 1, right = 1; right <= n; right++) {
if (a[right] >= x) num++;
//遇到大于等于x的数便计数
if (num == k) {
cnt += n - right + 1;
//加上该区域向右拓展的区域数量
while (a[left] < x) {
cnt += n - right + 1;
//加上该区域向左拓展的区域数量
left++;
}
//循环删除区域内左边比x小的节点
left++;
num--;
//为了往后遍历,区间第一个节点删除
//第一个节点一定大于等于x,所以num--
}
}
if (cnt >= m) return 1;
else return 0;
//M第M大的数,cnt为理应比M大的区域数量
//所以当cnt比M大时说明区域多了要缩小,所以返回1取更大的值
}
int main(){
cin>>t;
while(t--){
scanf("%d%d%lld",&n,&k,&m);
for(int i=1;i<=n;++i)
scanf("%d",&a[i]);
ll l=1,r=inf;//long long
while(l<=r){
ll mid=(l+r)>>1;
if(jg(mid)) l=mid+1;
else r=mid-1;
}
printf("%lld\n", l-1);
}
return 0;
}