[CSP-S 2021] 廊橋分配 題解
前言
這道題在考場上我花了足足3個小時,最后線段樹上二分的算法寫掛了,准備交個暴力,結果因為把國際航班的數量錯寫為國內航班的數量慘遭爆零,因為這道題,我徹底與1=無緣,為了警醒自己以后不要再犯低級錯誤,同時看到並沒有多少人使用線段樹二分做法,我決定寫下這篇題解,希望對你也能有所幫助
思路
看了題之后容易想到只需要讓每個廊橋上停留的飛機盡量多,分別求出國內航班和國際航班在有 \(n\) 個廊橋時每個廊橋所停留的飛機數,取一個前綴和然后 \(\Theta(n)\) 枚舉一遍求 \(\max\) 就能算出答案了
具體地說,先把飛機按照到達順序排序,對於這 \(n\) 個廊橋中的每一個廊橋我們開一個 \(last\) 數組,其中 \(last_i\) 表示第 \(i\) 個廊橋上最晚離開的飛機離開的時間,對於每一架飛機,我們需要找到滿足 \(last_i\) 小於飛機的着陸時間且 \(i\) 盡可能小的廊橋,將這個廊橋的停留飛機數加上一並把它的 \(last_i\) 更新為這架飛機的離開時間即可,找不到這樣的廊橋就說明不得不新使用一個未使用的廊橋,把廊橋總數加一並更新
那么問題(復雜度)的關鍵就來到了如何快速找到滿足 \(last_i\) 小於飛機的着陸時間且 \(i\) 盡可能小的廊橋
暴力
分析
很容易想到暴力從小到大枚舉廊橋,找到的滿足 \(last_i\) 小於飛機的着陸時間的第一個廊橋就是問題的解,找不到這樣的廊橋就說明不得不新使用一個未使用的廊橋
復雜度:\(\Theta(n(m_1+m_2))\)
期望得分:\(40\) 到 \(60\) (然而某谷的屑數據可以直接100)
c++ code
#include<bits/stdc++.h>
#define MAXN 100005
#define INF 2100000000
#define in read()
using namespace std;
struct Plane
{
int ar,le;//到達時間,離開時間
}pl[MAXN];
int lst[MAXN],layer,num[MAXN],tot[MAXN][2];//last數組,已使用廊橋數,廊橋容納飛機數,前綴和求答案數組
int n,m1,m2;//如題意
inline bool cmp(Plane x,Plane y){return x.ar<y.ar;}//排序
inline int read()//IO優化
{
int x=0;
char ch=getchar();
while(ch>'9'||ch<'0')ch=getchar();
while(ch>='0'&&ch<='9')x=x*10+(ch^48),ch=getchar();
return x;
}
void mwrite(int x)
{
if(x>9)mwrite(x/10);
putchar((x%10)|48);
}
void write(int x,char c)
{
mwrite(x<0?(putchar('-'),-x):x);
putchar(c);
}
signed main()
{
freopen("airport.in","r",stdin);
freopen("airport.out","w",stdout);
n=in,m1=in,m2=in;
//先處理國內航班,然后處理國際航班,前后無太大差異
for(int i=1;i<=m1;++i) pl[i].ar=in,pl[i].le=in;
sort(pl+1,pl+m1+1,cmp);//排序
for(int i=1,tmp;i<=m1;++i)
{
tmp=0;
for(int j=1;j<=layer;++j)
if(pl[i].ar>lst[j])//暴力枚舉所有廊橋,找到滿足的就退出
{
tmp=j;
break;
}
if(!tmp) lst[++layer]=pl[i].le,++num[layer];//沒找到就新加一層
else lst[tmp]=pl[i].le,++num[tmp];//找到了就更新信息
}
for(int i=1;i<=n;++i) tot[i][0]=num[i]+tot[i-1][0];//求前綴和
layer=0;
memset(num,0,sizeof(num));//一定要記得初始化
for(int i=1;i<=m2;++i) pl[i].ar=in,pl[i].le=in;
sort(pl+1,pl+m2+1,cmp);
for(int i=1,tmp;i<=m2;++i)
{
tmp=0;
for(int j=1;j<=layer;++j)
if(pl[i].ar>lst[j])
{
tmp=j;
break;
}
if(!tmp) lst[++layer]=pl[i].le,++num[layer];
else lst[tmp]=pl[i].le,++num[tmp];
}
for(int i=1;i<=n;++i) tot[i][1]=num[i]+tot[i-1][1];
int ans=-1;
for(int i=0;i<=n;++i)ans=max(ans,tot[i][0]+tot[n-i][1]);//枚舉一遍所有可能求最大值
write(ans,'\n');
return 0;
}
正解
分析
想要優化復雜度,只能在尋找滿足要求的廊橋上下功夫,可以嘗試使用數據結構優化
假設第 \(i\) 架飛機於 \(ar_i\) 到達,那么顯然當且僅當 \(last\) 的最小值小於 \(ar_i\) 時才能繼續使用已經用過的廊橋,那么我們在暴力中枚舉編號最小的滿足條件的廊橋就可以換一種看法,變成“我們需要從 \(1\) 開始不斷擴大 \(j\) 直到編號為 \(1\) 到 \(j\) 的廊橋中 \(last\) 的最小值小於 \(ar_i\)”

也就是說我們要找到這樣一個 \(i\),使得 \(last_1\) 到 \(last_{i-1}\) 的最小值大於 \(ar_j\) 且 \(last_{1}\) 到 \(last_{i}\) 的最小值小於 \(ar_j\)
這樣的 \(i\) 顯然只有一個,那么容易想到用線段樹來維護區間最小值,然后在 \(last_{1}\) 到 \(last_{layer}\) 二分查找對應的 \(i\),如果最小值小於 \(ar_j\) 就去前半段繼續找,否則就去后半段找
對於每一架飛機需要二分找一遍,每一次查詢最小值復雜度 \(\Theta(\log_2n)\),二分法最壞情況查詢 \(\Theta(\log_2n)\) 次,也就是說對於每架飛機需要 \(\Theta(\log^2_2n)\) 來處理
復雜度:\(\Theta((m_1+m_2)\log^2_2n)\)
期望得分:100
c++ code
#include<bits/stdc++.h>
#define MAXN 100005
#define INF 2100000000
#define in read()
using namespace std;
struct Plane
{
int ar,le;//到達時間,離開時間
}pl[MAXN];
struct Seg//線段樹板子
{
struct Node
{
int ls,rs,v;//維護最小值
}node[MAXN<<2];
void build(int l,int r,int pos)//建樹
{
node[pos].ls=l,node[pos].rs=r,node[pos].v=0;//最開始last值都為0
if(l==r)return;
int mid=(l+r)>>1;
build(l,mid,pos<<1);
build(mid+1,r,(pos<<1)+1);
}
void change(int x,int v,int pos)//單點修改
{
if(node[pos].ls==node[pos].rs)
{
node[pos].v=v;
return;
}
int mid=(node[pos].ls+node[pos].rs)>>1;
if(x<=mid)change(x,v,pos<<1);
else change(x,v,(pos<<1)+1);
node[pos].v=min(node[pos<<1].v,node[(pos<<1)+1].v);
}
int ask(int l,int r,int pos)//區間查詢最小值
{
if(l<=node[pos].ls&&node[pos].rs<=r) return node[pos].v;
int ans=INF,mid=(node[pos].ls+node[pos].rs)>>1;
if(l<=mid) ans=min(ans,ask(l,r,pos<<1));
if(r>mid) ans=min(ans,ask(l,r,(pos<<1)+1));
return ans;
}
}lst;
int layer,num[MAXN],tot[MAXN][2];//已使用廊橋,廊橋容納飛機數,前綴和統計答案
int n,m1,m2;//如題意
inline bool cmp(Plane x,Plane y){return x.ar<y.ar;}
inline int read()//IO優化
{
int x=0;
char ch=getchar();
while(ch>'9'||ch<'0')ch=getchar();
while(ch>='0'&&ch<='9')x=x*10+(ch^48),ch=getchar();
return x;
}
void mwrite(int x)
{
if(x>9)mwrite(x/10);
putchar((x%10)|48);
}
void write(int x,char c)
{
mwrite(x<0?(putchar('-'),-x):x);
putchar(c);
}
signed main()
{
freopen("airport.in","r",stdin);
freopen("airport.out","w",stdout);
n=in,m1=in,m2=in;
//先處理國內航班
for(int i=1;i<=m1;++i) pl[i].ar=in,pl[i].le=in;
sort(pl+1,pl+m1+1,cmp);//按到達時間排序
lst.build(1,m1,1);//建樹
for(int i=1,ll,rr,mm;i<=m1;++i)//枚舉飛機
{
ll=0,rr=layer;//二分
while(ll<rr)
{
mm=(ll+rr)>>1;
if(pl[i].ar>lst.ask(1,mm,1)) rr=mm;//滿足就繼續找更小的
else ll=mm+1;//不滿足就繼續找更大的
}
if(ll==layer)//如果找到最后一個那么可能是無解,需要使用新廊橋
if(pl[i].ar<lst.ask(1,ll,1))
ll=0;
if(!ll) lst.change(++layer,pl[i].le,1),++num[layer];//不行就使用新的廊橋
else lst.change(ll,pl[i].le,1),++num[ll];//可行就更新廊橋信息
}
for(int i=1;i<=n;++i) tot[i][0]=num[i]+tot[i-1][0];//前綴和統計答案
//國際航班
layer=0;
memset(num,0,sizeof(num));//記得初始化
for(int i=1;i<=m2;++i) pl[i].ar=in,pl[i].le=in;
sort(pl+1,pl+m2+1,cmp);
lst.build(1,m2,1);
for(int i=1,ll,rr,mm,tmp;i<=m2;++i)
{
ll=0,rr=layer;
while(ll<rr)
{
mm=(ll+rr)>>1;
if(pl[i].ar>lst.ask(1,mm,1)) rr=mm;
else ll=mm+1;
}
if(ll==layer)
if(pl[i].ar<lst.ask(1,ll,1))
ll=0;
if(!ll) lst.change(++layer,pl[i].le,1),++num[layer];
else lst.change(ll,pl[i].le,1),++num[ll];
}
for(int i=1;i<=n;++i) tot[i][1]=num[i]+tot[i-1][1];
int ans=-1;
for(int i=0;i<=n;++i)ans=max(ans,tot[i][0]+tot[n-i][1]);//遍歷枚舉答案
write(ans,'\n');
return 0;
}
該文為本人原創,轉載請注明出處
