倍增法—国旗计划

直达原题

题目描述

A 国正在开展一项伟大的计划 —— 国旗计划。这项计划的内容是边防战士手举国旗环绕边境线奔袭一圈。这项计划需要多名边防战士以接力的形式共同完成,为此,国土安全局已经挑选了 \(N\) 名优秀的边防战上作为这项计划的候选人。

A 国幅员辽阔,边境线上设有 \(M\) 个边防站,顺时针编号 \(1\)\(M\)。每名边防战士常驻两个边防站,并且善于在这两个边防站之间长途奔袭,我们称这两个边防站之间的路程是这个边防战士的奔袭区间。\(N\) 名边防战士都是精心挑选的,身体素质极佳,所以每名边防战士的奔袭区间都不会被其他边防战士的奔袭区间所包含。

现在,国十安全局局长希望知道,至少需要多少名边防战士,才能使得他们的奔袭区间覆盖全部的边境线,从而顺利地完成国旗计划。不仅如此,安全局局长还希望知道更详细的信息:对于每一名边防战士,在他必须参加国旗计划的前提下,至少需要多少名边防战士才能覆盖全部边境线,从而顺利地完成国旗计划。

输入格式

第一行,包含两个正整数 \(N,M\),分别表示边防战士数量和边防站数量。

随后 \(N\) 行,每行包含两个正整数。其中第 \(i\) 行包含的两个正整数 \(C_i\)\(D_i\) 分别表示 \(i\) 号边防战士常驻的两个边防站编号,\(C_i\) 号边防站沿顺时针方向至 \(D_i\) 号边防站力他的奔袭区间。数据保证整个边境线都是可被覆盖的。

输出格式

输出数据仅 \(1\) 行,需要包含 \(N\) 个正整数。其中,第 \(j\) 个正整数表示 \(j\) 号边防战士必须参加的前提下至少需要多少名边防战士才能顺利地完成国旗计划。

样例 #1

样例输入 #1

4 8
2 5
4 7
6 1
7 3

样例输出 #1

3 3 4 3

提示

\(N\leqslant 2×10^5,M<10^9,1\leqslant C_i,D_i\leqslant M\)

分析

题目要求很清晰:计算能够覆盖整个圆周的最少区间,首先看到题目里的圆圈,第一步,断环为链,为了保存原来首尾关系我们在断开的尾部紧接着把他复制一遍,形成首 ****** 尾首 ****** 尾。第二步,贪心法找下一个最优区间选择一个区间i后,下一个区间只能从左端点小于或等于i的右端点的那些区间中选择,在这些区间中选择右端点最大的那个区间,是最优的。但是题目要求以每一个区间为起点,这样复杂度就成了O(n2 )自然超时,下一步就是去优化。第三步,倍增法定义go[s][i]表示从第s个区间出发,走2个最优区间后到达的区间。例如,go[s][4]是从s出发到达的第24=16个最优的区间,s和go[s][4]之间的区间也都是最优的。跳的时候先用大数再用小数符合二进制特征。这样总的复杂度就是O(nlog2n)+O(nlog2n)。

代码实现

#include<bits/stdc++.h>
using namespace std;
const int N = 4e5+1;
int n, m;
struct warrior{   
    int id, L, R;                 //id:战士的编号;L、R,战士的左右区间
    bool operator < (const warrior b) const{return L < b.L;}
}w[N*2];
int n2;
int go[N][20];    
void init(){                      //贪心 + 预计算倍增
     int nxt = 1;
     for(int i=1;i<=n2;i++){      //用贪心求每个区间的下一个区间
         while(nxt<=n2 && w[nxt].L<=w[i].R) 
            nxt++;                //每个区间的下一个是右端点最大的那个区间
         go[i][0] = nxt-1;        //区间i的下一个区间
     }
     for(int i=1;(1<<i)<=n;++i)   //倍增:i=1,2,4,8,... 共log(n)次
         for(int s=1;s<=n2;s++)   //每个区间后的第2^i个区间
             go[s][i] = go[go[s][i-1]][i-1];
}
int res[N];
void getans(int x){                  //从第x个战士出发
     int len=w[x].L+m, cur=x, ans=1;
     for(int i=log2(N);i>=0;i--){     //从最大的i开始找:2^i = N
         int pos = go[cur][i];
         if(pos && w[pos].R < len){
            ans += 1<<i;               //累加跳过的区
            cur = pos;                 //从新位置继续开始
         }
     }
     res[w[x].id] = ans+1;
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++){
        w[i].id = i;                   //记录战士的顺序
        scanf("%d%d",&w[i].L, &w[i].R);
        if(w[i].R < w[i].L)    w[i].R += m;      //把环变成链            
    }
    sort(w+1, w+n+1);  //按左端点排序
    n2 = n;
    for(int i=1;i<=n;i++)  //拆环加倍成一条链
    {    n2++;  w[n2]=w[i];  w[n2].L=w[i].L+m;  w[n2].R=w[i].R+m;  }
    init();
    for(int i=1;i<=n;i++)  getans(i);         //逐个计算每个战士
    for(int i=1;i<=n;i++)  printf("%d ",res[i]);
    return 0;
}
posted @ 2023-07-28 00:36  LongDz  阅读(83)  评论(0)    收藏  举报