1. 程式人生 > 其它 >[NOIP2020-test2]公交車(線段樹+貪心)

[NOIP2020-test2]公交車(線段樹+貪心)

SDFZ,[NOIP2020test2]公交車的題解,包含原始貪心和線段樹優化貪心兩份程式碼。

【題目描述】

U142335 公交車

\(\text{LYK}\) 在玩一個遊戲。

\(k\) 群小怪獸想乘坐公交車。第 \(i\) 群小怪獸想從 \(xi\) 出發乘坐公交車到 \(yi\)。但公交車的容量只有 \(M\),而且這輛公交車只會從 \(1\) 號點行駛到 \(n\) 號點。

\(\text{LYK}\) 想讓小怪獸們儘可能的到達自己想去的地方。它想知道最多能滿足多少小怪獸的要求。

當然一群小怪獸沒必要一起上下車,它們是可以被分開來的。

對於 \(30\) % 的資料小怪獸的總數不超過 \(10\) 只,\(n≤10\)

對於 \(60\) % 的資料 \(k,n≤1000\)

對於 \(100\)

% 的資料 \(1≤n≤20000\)\(1≤k≤50000\)\(1≤M≤100\)\(1≤ci≤100\)\(1≤xi<yi≤n\)


【解題思路】

對於 \(k≤50000\) 的資料範圍, \(O(n^2)\) 的DP是可行的。但很遺憾,我並不會DP,所以選擇貪心。

將公交車行駛的路線看作整體區間,小怪獸想從 \(xi\) 走到 \(yi\) 就可以抽象為區間 \([1,n]\) 的子區間 \([xi,yi]\) ,這樣,我們將這道題目轉化為了經典的貪心模型:“活動安排“。

如果對這一模型也沒有印象,可以嘗試一下這道題目:T142557 [SDFZ-test-01]活動安排 Act

貪心策略

\(i\)

群小怪獸想從 \(xi\) 出發乘坐公交車到 \(yi\) ,我們將這樣的位置改變稱為一次移動,\(xi\) 是始發站, \(yi\) 是終到站。

我們對所有的移動進行按終到站從小到大排序,這樣使得每一次移動儘量靠近整個區間的左端,而終到站相同,我們按始發站從大到小排序,這樣使得選中的每一次移動都儘量靠近整個區間的右端,這樣,每一次移動的區間長度都會最小,從而得到整體最優解。

選擇第一次移動作為初始移動,遍歷剩下的排完序的每一次移動,當下一次移動的起始時間大於等於前一個移動的終到站時,小怪物們就可以到達,否則不能。

容量限制

為了避免公交車擠上去超過 \(M\) 只小怪物,淪為一列印度火車,我們需要維護一個數組 \(f\)

來記錄在第 \(i\) 站時,車上有多少隻小怪物

假設現在我們已經取出了一次移動 \([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;
}

THE END