Excellent Engineer (線段樹)(個人詳解)
這道題當時做的時候根本沒有想到用線段樹,其實這道題用的完全是線段樹的基本用法,寫起來也比較簡單。
但是對我們這種新手來講真的不好想。
花店時間好好解釋下吧,也便於以後複習看看
You are working for an agency that selects the best software engineers from Belgium, the Netherlands and Luxembourg for employment at various international companies. Given the very large number of excellent software engineers these countries have to offer, the agency has charged you to develop a tool that quickly selects the best candidates available from the agency's files.
Before a software engineer is included in the agency's files, he has to undergo extensive testing. Based on these tests, all software engineers are ranked on three essential skills: communication skills, programming skills, and algorithmic knowledge. The software engineer with rank one in the category algorithmic knowledge is the best algorithmic expert in the files, with rank two the second best, etcetera.
For potential customers, your tool needs to process the agency's files and produce a shortlist of the potentially most interesting candidates. A software engineer is a potential candidate that is to be put on this shortlist if there is no other software engineer in the files that scores better on all three skills. That is, an engineer is to be put on the shortlist if there is no other software engineer that has better communication skills, better programming skills, and more algorithmic knowledge.
Input Format
On the first line one positive number: the number of test cases, at most 100100. After that per test case:
- one line with a single integer nn (1 \le n \le 10^5)(1≤n≤105): the number of software engineers in the agency's files.
- nn lines, each with three space-separated integers r_1r1, r_2r2 and r_3r3 (1 \le r_1, r_2, r_3 \le n)(1≤r1,r2,r3≤n): the rank of each software engineer from the files with respect to his communication skills, programming skills and algorithmic knowledge, respectively.
For each skill ss and each rank xx between 11 and nn, there is exactly one engineer with r_s = xrs=x.
Output Format
Per test case:
- one line with a single integer: the number of candidates on the shortlist.
樣例輸入
3 3 2 3 2 3 2 3 1 1 1 3 1 2 3 2 3 1 3 1 2 10 1 7 10 3 9 7 2 2 9 5 10 8 4 3 5 7 5 2 6 1 3 9 6 6 8 4 4 10 8 1
樣例輸出
1 3 7
題目來源
題目意思很好懂,找出一些人,對於這些人,他們的三項能力值沒有人比他同時都高,輸出的是人數。
但是這麼想都是一個O(n²)的演算法,想優化到O(n*log(n)) 感覺還是要點腦筋。
我們將三個能力分別記為a,b,c
我們首先按第一個能力排一下序,然後模擬一下,發現排序是可行的, 然後傳統的暴力就是找遍歷前面選中的人,看他們是否有任意一個人比當前判斷的人三項值都小(實際上只用判斷兩個值b,c),如果存在一個人,那麼就說明選中的人裡面有人比這個人強,所以不能將這個人選進去。。。。 那麼這是n²的演算法。
//如果基礎不好的,建議寫一下暴力,多測幾組資料,然後再來寫線段樹
其實遍歷前面選中的人是可以進行優化的。
第一,只需要判斷b和c的值
第二,也是最重要的,我們記當前判斷的人的三個值為 x,y,z,我們只需要在選中的人中 先選出 b值在 1-y 之間的人,判斷是不是這些人的c值都比 z小, 其實這還不是最終的優化, 最後我們是找出 b值在1-y之間 的人 所對應的最小 c值, 只要z小於這個最小值c,就說明他可以入圍,因為雖然別人的b值比他小,但是c值比不上這個判斷的人,反之不能入圍。
還是舉個例子吧,感覺還是不是那麼好懂。
1 4 7
2 5 6
3 6 8
4 7 1
第一個人 1 4 7 一開始直接被選中
那麼第二個人 b值為 5,我要去找之前選中的人中 b值在1-5的人,找出他們的最小c值,這裡是7(第一個人的),6是小於7的,所以他可以入圍,因為他的c值為6,是非常小的了,非常靠前了。
第三個人 找之前b值1-6,最小c值為6, 8是大於6的,所以他不能進去
第四個人和第二個人一樣,可以入圍。
總之,我們就是要判斷之前選中的人裡面有沒有一個人比這個待定的人更優秀,所謂的優秀就是三項值都要強,而第一項值是排了序的了,所以是遞增的,不用考慮, 第二項值之所以要在1-b中找 而不是向 b到+∞找 是因為1-b代表的是可能更優秀的人群,而我們主要的任務就是找有沒有一個人比他更優秀,如果有,他就不能進去。 然後c值,c值決定這個人能否入圍,
c值如果比之前的c值要小,就說明他也非常優秀(有一技之長),就可以入圍,但是這個人的c值一旦大於入圍的人最小的c值
,就說明入圍的人中,不僅有人b值比他小,c值也比他小,而a值就更不用說了,排序了的,所以不能行。
這樣,我們就簡化了判斷,而且可以和線段樹扯上關係。
下面將如何應用到線段樹上。
個人覺得構造線段樹要思考兩個問題,①拿什麼做範圍,②值存什麼
根據前面的過程,我們取b值做範圍,值存最小c值。
所以,每次新加入一個人,我們就在 position = b 的為值插入 c(b,c都是這個人的值),然後更新線段樹
判斷就是查詢1-b的最小c值,就是一個普通的query函式就解決了,所以,寫起來是非常簡單的。
然後,每個人寫線段樹的風格都非常不同,所以主要還是看思路,程式碼真的非常簡單了
歡迎留言
#include <iostream>
#include<cstdio>
#define inf 0x3f3f3f3f
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn = 1e5+5;
#define mid (l+r)>>1
#define lef rt<<1
#define rig rt<<1|1
struct spe
{
int a,b,c;
};
int L,R; //查詢和更改函式的大界限
spe p[maxn];
struct segtree
{
int mi; //範圍內最小的 第三能力值
};
segtree tree[maxn<<3];
bool cmp( spe t1,spe t2 ) //按照第一種能力排序
{
return t1.a < t2.a;
}
void change( int rt )
{
tree[rt].mi = min( tree[lef].mi,tree[rig].mi );
}
int query( int l,int r,int rt ) //詢問範圍內最小的 第三能力值 c
{
if( l >= L && r <= R )
return tree[rt].mi;
int m = mid;
int ans = inf;
if( m >= L )
ans = min( ans,query(l,m,lef) );
if( m < R )
ans = min( ans,query(m+1,r,rig) );
return ans;
}
void update( int l,int r,int rt,int pos,int val ) //本題的更新是點更新
{
if( l == r )
{
tree[rt].mi = val;
return;
}
int m = mid;
if( m >= pos )
update(l,m,lef,pos,val);
else update(m+1,r,rig,pos,val);
change(rt);
}
int main()
{
int T,n;
scanf("%d",&T);
while( T-- )
{
memset(tree,inf,sizeof tree);
scanf("%d",&n);
for( int i = 1 ; i <= n ; i++ )
scanf("%d %d %d",&p[i].a,&p[i].b,&p[i].c);
sort(p+1,p+1+n,cmp);
int ans = 0;
for( int i = 1 ; i <= n ; i++ )
{
L = 1,R = p[i].b; //線段樹查詢的範圍
int t = query(1,n,1);
if( p[i].c < t )
{
ans++;
update(1,n,1,p[i].b,p[i].c);
}
}
printf("%d\n",ans);
}
return 0;
}