bzoj4444: [Scoi2015]国旗计划

博客介绍了Scoi2015比赛中的国旗计划问题,通过分析区间互不包含的特点,提出左端点在区间内最右边的区间为最优解的策略。将环转化为链,利用倍增算法找到祖先节点,并通过贪心思想,以第一个区间答案为基础,调整后续答案验证解法。文章提供了问题解法和代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目传送门
好题

解法:
因为区间互不包含。
那么左端点在区间内最右边的的区间一定是当前区间能扩展到最远的区间。
这句话有点绕。举个例子。

假设当前区间为1~5。
那么我们往右扩展。
找到左端点最右且小于等于5的区间。
这个区间一定是最优解。
因为区间互不包含啊。如果这个区间不是最优解的话那么就被别的包含了。。

因为题目是环。需要转化成链。
所以把环复制一份放在后面就变成了链了。

如果当前区间起点为l。整个环长度为mx。
那么我们需要覆盖l到l+mx

然后我们把这个区间能扩展到的最右端看做一个父子关系。
从l到l+mx这个过程我们可以看做找到一个祖先是大于等于l+mx的。
这个过程倍增实现就行。

然后通过贪心可以发现每个区间参与的答案相差都不超过1的。
那么我们求出第一个区间参与的答案。
以后的答案我们分别用前面的答案-1,0,+1来验证即可

代码实现:

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<queue>
using namespace std;
int L[210000],R[210000],l[210000],r[210000],A[410000];
bool cmp(int a,int b) {return a<b;}int n,m,len;
int pos(int x) {
    int l=1,r=len,mid,ans=0;
    while(l<=r) {
        mid=(l+r)/2;
        if(A[mid]>=x) {
            if(A[mid]==x)ans=mid;
            r=mid-1;
        }else l=mid+1;
    }return ans;
}
struct node {int l,r;}s[410000];int mx[21][1100000],bin[21];
bool cmp1(node n1,node n2) {return n1.l<n2.l;}
int jump(int x,int k) {for(int i=20;i>=0;i--)if((k&bin[i]))x=mx[i][x];return x;}
int main() {
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++) {scanf("%d%d",&L[i],&R[i]);A[++len]=L[i];A[++len]=R[i];}
    sort(A+1,A+1+len,cmp);int mmax=0;
    for(int i=1;i<=n;i++) {l[i]=pos(L[i]);r[i]=pos(R[i]);mmax=max(mmax,max(l[i],r[i]));}
    len=0;
    for(int i=1;i<=n;i++) {
        if(l[i]>r[i])r[i]+=mmax;
        s[++len].l=l[i];s[len].r=r[i];
        s[++len].l=l[i]+mmax;s[len].r=min(r[i]+mmax,2*mmax);
    }sort(s+1,s+1+len,cmp1);
    int t=1,F=0;memset(mx,0,sizeof(mx));
    for(int i=1;i<=2*mmax;i++) {
        while(s[t].l<=i&&t<=2*n) {F=s[t].r;t++;}
        mx[0][i]=F;
    }
    bin[0]=1;for(int i=1;i<=20;i++)bin[i]=bin[i-1]*2;
    for(int j=1;j<=20;j++)for(int i=1;i<=2*mmax;i++)mx[j][i]=mx[j-1][mx[j-1][i]];
    int ans;
    for(ans=0;ans<n;ans++)if(jump(r[1],ans)>=l[1]+mmax) break;
    printf("%d",ans+1);
    for(int i=2;i<=n;i++) {
        if(jump(r[i],ans-1)>=l[i]+mmax)printf(" %d",ans);
        else if(jump(r[i],ans)>=l[i]+mmax)printf(" %d",ans+1);
        else printf(" %d",ans+2);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值