[NOIP2020-test2]公交車(線段樹+貪心)
【題目描述】
\(\text{LYK}\) 在玩一個遊戲。
有 \(k\) 群小怪獸想乘坐公交車。第 \(i\) 群小怪獸想從 \(xi\) 出發乘坐公交車到 \(yi\)。但公交車的容量只有 \(M\),而且這輛公交車只會從 \(1\) 號點行駛到 \(n\) 號點。
\(\text{LYK}\) 想讓小怪獸們儘可能的到達自己想去的地方。它想知道最多能滿足多少小怪獸的要求。
當然一群小怪獸沒必要一起上下車,它們是可以被分開來的。
對於 \(30\) % 的資料小怪獸的總數不超過 \(10\) 只,\(n≤10\)。
對於 \(60\) % 的資料 \(k,n≤1000\)。
對於 \(100\)
【解題思路】
對於 \(k≤50000\) 的資料範圍, \(O(n^2)\) 的DP是可行的。但很遺憾,我並不會DP,所以選擇貪心。
將公交車行駛的路線看作整體區間,小怪獸想從 \(xi\) 走到 \(yi\) 就可以抽象為區間 \([1,n]\) 的子區間 \([xi,yi]\) ,這樣,我們將這道題目轉化為了經典的貪心模型:“活動安排“。
如果對這一模型也沒有印象,可以嘗試一下這道題目:T142557 [SDFZ-test-01]活動安排 Act
貪心策略
第 \(i\)
我們對所有的移動進行按終到站從小到大排序,這樣使得每一次移動儘量靠近整個區間的左端,而終到站相同,我們按始發站從大到小排序,這樣使得選中的每一次移動都儘量靠近整個區間的右端,這樣,每一次移動的區間長度都會最小,從而得到整體最優解。
選擇第一次移動作為初始移動,遍歷剩下的排完序的每一次移動,當下一次移動的起始時間大於等於前一個移動的終到站時,小怪物們就可以到達,否則不能。
容量限制
為了避免公交車擠上去超過 \(M\) 只小怪物,淪為一列印度火車,我們需要維護一個數組 \(f\)
假設現在我們已經取出了一次移動 \([l,r]\) ,只需要從 \(l\) 到 \(r\) 遍歷 \(f\) 陣列,求出最大值以及最小殘餘容量 \((M-MAX)\) ,就能得到 \(T=\min(w,(M-MAX))\) 。——這裡的T表示,在當前移動中,實際上有多少小怪物能上車。
程式碼如下:
for(int i = l; i < r; ++i) MAX = max(MAX,f[i]);
T = min(w,M - MAX);
for(int i = l; i < r; ++i) f[i] += T;
憑藉這個簡單的貪心,我們能夠獲得 60 分的好成績。
線段樹優化
在上文給出的程式碼中,時間複雜度的瓶頸在於兩點:區間最大值和區間加法。顯然,我們可以用線段樹做一個卓有成效的優化,將時間複雜度降低至 \(O(klogn)\) 。
線段樹需要記錄區間max值,具體不作贅述,詳見AC程式碼。
程式碼(60 pts & 100 pts)
//貪心,60 pts
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long ll;
struct Seg
{
int l,r,w;
bool operator <(const Seg &a)const {return r < a.r;}
};
Seg s[50010];
int f[50010];//每一時刻 bus 上的人數
int main()
{
int k,n,M;ll ans = 0;
scanf("%d%d%d",&k,&n,&M);
for(int i = 1; i <= k; ++i) scanf("%d%d%d",&s[i].l,&s[i].r,&s[i].w);
sort(s+1,s+n+1);//按右端點排序
int now = 0;
for(int x = 1; x <= n; ++x)
{
int MAX = 0,MIN = 0;
int l = s[x].l,r = s[x].r,w = s[x].w;
now = l;
for(int i = l; i < r; ++i) MAX = max(MAX,f[i]);
MIN = min(w,M - MAX);
for(int i = l; i < r; ++i) f[i] += MIN;
ans += MIN;
}
printf("%lld\n",ans);
return 0;
}
//線段樹優化貪心, 100pts
#include<iostream>
#include<cstdio>
#include<algorithm>
#define reg register
using namespace std;
const int N = 50000 + 10;
typedef long long ll;
struct Seg{int l,r,w;};
bool cmp(Seg a,Seg b)
{
if(a.r != b.r) return a.r < b.r;
else return a.l > b.l;//按右端點排,相同的比較長短
}
struct SegmentTree{int l,r,maxx,tag;};
#define ls p<<1
#define rs p<<1|1
#define mid ((t[p].l+t[p].r)>>1)
SegmentTree t[N << 2];
Seg s[N];
void refresh(int p){t[p].maxx = max(t[ls].maxx,t[rs].maxx);}
void build(int p,int l,int r)
{
t[p].l = l,t[p].r = r,t[p].maxx = 0,t[p].tag = 0;
if(l == r) return;
build(ls,l,mid);
build(rs,mid + 1,r);
refresh(p);
}
void pushup(int p,int v)
{
t[p].maxx = t[p].maxx + v;
t[p].tag = t[p].tag + v;
}
void pushdown(int p)
{
if(!t[p].tag) return;
pushup(ls,t[p].tag);
pushup(rs,t[p].tag);
t[p].tag = 0;
}
void update(int p,int l,int r,int v)
{
if(l <= t[p].l && t[p].r <= r) return pushup(p,v);
pushdown(p);
if(l <= mid) update(ls,l,r,v);
if(r > mid) update(rs,l,r,v);
refresh(p);
}
inline int query(int p,int l,int r)
{
if(l <= t[p].l && t[p].r <= r) return t[p].maxx;
pushdown(p);
int res = 0;
if(l <= mid) res = max(res,query(ls,l,r));
if(r > mid) res = max(res,query(rs,l,r));
return res;
}
int main()
{
int k,n,M,ans = 0;
scanf("%d%d%d",&k,&n,&M);
for(reg int i = 1; i <= k; ++i) scanf("%d%d%d",&s[i].l,&s[i].r,&s[i].w);
sort(s+1,s+k+1,cmp);//按右端點排序
build(1,1,n);//建樹
int now = 0;
for(reg int i = 1; i <= k; ++i)//切記不要把 k 寫成 n !
{
reg int MAX = 0,MIN = 0;
reg int l = s[i].l,r = s[i].r,w = s[i].w;
MAX = query(1,l,r-1);//注意是[l,r-1]
MIN = min(w,M - MAX);
update(1,l,r-1,MIN);
ans = ans + MIN;
}
printf("%d\n",ans);
return 0;
}