题目:
小小粉丝度度熊
Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total Submission(s): 1326 Accepted Submission(s): 428
Problem Description
度度熊喜欢着喵哈哈村的大明星——星星小姐。
为什么度度熊会喜欢星星小姐呢?
首先星星小姐笑起来非常动人,其次星星小姐唱歌也非常好听。
但这都不是最重要的,最重要的是,星星小姐拍的一手好代码!
于是度度熊关注了星星小姐的贴吧。
一开始度度熊决定每天都在星星小姐的贴吧里面签到。
但是度度熊是一个非常健忘的孩子,总有那么几天,度度熊忘记签到,于是就断掉了他的连续签到。
不过度度熊并不是非常悲伤,因为他有m张补签卡,每一张补签卡可以使得某一忘签到的天,变成签到的状态。
那么问题来了,在使用最多m张补签卡的情况下,度度熊最多连续签到多少天呢?
为什么度度熊会喜欢星星小姐呢?
首先星星小姐笑起来非常动人,其次星星小姐唱歌也非常好听。
但这都不是最重要的,最重要的是,星星小姐拍的一手好代码!
于是度度熊关注了星星小姐的贴吧。
一开始度度熊决定每天都在星星小姐的贴吧里面签到。
但是度度熊是一个非常健忘的孩子,总有那么几天,度度熊忘记签到,于是就断掉了他的连续签到。
不过度度熊并不是非常悲伤,因为他有m张补签卡,每一张补签卡可以使得某一忘签到的天,变成签到的状态。
那么问题来了,在使用最多m张补签卡的情况下,度度熊最多连续签到多少天呢?
Input
本题包含若干组测试数据。
第一行两个整数n,m,表示有n个区间,这n个区间内的天数,度度熊都签到了;m表示m张补签卡。
接下来n行,每行两个整数(l[i],r[i]),表示度度熊从第l[i]天到第r[i]天,都进行了签到操作。
数据范围:
1<=n<=100000
0<=m<=1000000000
0<=l[i]<=r[i]<=1000000000
注意,区间可能存在交叉的情况。
第一行两个整数n,m,表示有n个区间,这n个区间内的天数,度度熊都签到了;m表示m张补签卡。
接下来n行,每行两个整数(l[i],r[i]),表示度度熊从第l[i]天到第r[i]天,都进行了签到操作。
数据范围:
1<=n<=100000
0<=m<=1000000000
0<=l[i]<=r[i]<=1000000000
注意,区间可能存在交叉的情况。
Output
输出度度熊最多连续签到多少天。
Sample Input
2 1 1 1 3 3 1 2 1 1
Sample Output
3 3Hint样例一:度度熊补签第2天,然后第1天、第二天和第三天都进行了签到操作。 样例二:度度熊补签第2天和第3天。
code1: 前缀和 + 二分 + 线段合并
#include <stdio.h>
#include <iostream>
#include <set>
#include <algorithm>
#include <iomanip>
#include <string.h>
#include <map>
#include <set>
#include <math.h>
using namespace std;
const int maxn=105000;
typedef long long ll;
int n,tot;///合并前后的线段数
ll m;
struct node{
ll l, r;
}rec[maxn],a[maxn];
ll pre[maxn];
bool operator<(const node& a, const node& b){
return a.l<b.l;
}
///为哥思路: 前缀和 + 二分 + 线段合并
int main(){
while(~scanf("%d%lld", &n, &m)){
for(int i=1;i<=n;i++){
scanf("%lld%lld", &rec[i].l, &rec[i].r);
}
sort(rec+1, rec+1+n);///按照左端点排序
tot=0;
int last=-2;
for(int i=1;i<=n;i++){///相交的区间进行合并 后面处理起来方便很多
if(rec[i].l>last+1){
++tot;
a[tot].l=rec[i].l;
a[tot].r=rec[i].r;
}
else {
a[tot].r=max(a[tot].r, rec[i].r);
}
last=a[tot].r;
}
pre[1]=0;
for(int i=2;i<=tot;i++){
pre[i]=pre[i-1]+a[i].l-a[i-1].r-1;///预处理出前 i 段线段中包含的没签到的天数
}
ll ans=0;
for(int i=1;i<=tot;i++){///枚举每一个线段的左端点,二分寻找最远的右端点使得两点之间所有天数都被覆盖
int l=i, r=tot;
int tag=-1;///记录最远线段编号
while(l<=r){
if(r-l<=1){
if(pre[r]-pre[i]<=m) tag=r;
else tag=l;
break;
}
int mid=(l+r)/2;
if(pre[mid]-pre[i]<=m) l=mid;
else r=mid;
}
ll p=a[tag].r-a[i].l+1;
ll add=m-(pre[tag]-pre[i]);///a[i].l 到 a[tag].r中没用完的m 补在后面
ans=max(ans, p+add);
}
printf("%lld\n", ans);
}
return 0;
}
code2:线段合并 + 尺取
#include <stdio.h>
#include <iostream>
#include <set>
#include <algorithm>
#include <iomanip>
#include <string.h>
#include <map>
#include <set>
#include <math.h>
using namespace std;
const int maxn=105000;
typedef long long ll;
int n,tot;///合并前后的线段数
ll m,pre[maxn];
struct node{
ll l, r;
}rec[maxn],a[maxn];
bool operator<(const node& a, const node& b){
return a.l<b.l;
}
///另一种思路:线段合并 + 尺取
int main(){///202MS 2864K
while(scanf("%d%lld", &n, &m)==2){
for(int i=1;i<=n;i++){
scanf("%lld%lld", &rec[i].l, &rec[i].r);
}
sort(rec+1, rec+1+n);///按照左端点排序
tot=0;
int last=-2;
for(int i=1;i<=n;++i){///相交的区间进行合并 后面处理起来方便很多
if(rec[i].l>last+1){
++tot;
a[tot].l=rec[i].l;
a[tot].r=rec[i].r;
}
else {
a[tot].r=max(a[tot].r, rec[i].r);
}
last=a[tot].r;
}
pre[0]=0;
for(int i=1;i<tot;++i){
pre[i]=a[i+1].l-a[i].r-1;/// pre[i]表示 i 和 i+1 两段区间之间空余的天数
}
ll ans=0;
int usedm=0,s=1,t=1;
for(;t<tot;++t){///枚举每一段空余天数 作为尺取右端点
usedm+=pre[t];
while(usedm>m){
usedm-=pre[s];
s++;
}
ans=max(ans,a[t+1].r-a[s].l+1 + (m-usedm));
}
for(int i=1;i<=tot;++i) ///考虑任意两段都连不起来的情况
ans=max(ans,a[1].r-a[1].l+1 + m);
printf("%lld\n", ans);
}
return 0;
}