1. 程式人生 > >[USACO11NOV]牛的陣容Cow Lineup

[USACO11NOV]牛的陣容Cow Lineup

【問題描述】

農民約翰僱一個專業攝影師給他的部分牛拍照。由於約翰的牛有好多品種,他喜歡他的照片包含每

個品種的至少一頭牛。

約翰的牛都站在一條沿線的不同地方, 每一頭牛由一個整數位置 X_i以及整數品種編號 ID_i表示。

約翰想拍一張照片,這照片由沿線的奶牛的連續範圍組成。照片的成本與規模相當,這就意味著,在一

系列照片中的最大和最小 X 座標的差距決定了照片的成本。

請幫助約翰計算最小的照片成本,這些照片中有每個不同的品種的至少一頭牛,沒有兩頭牛願意站

在同一個地點的。

【輸入格式】

第 1 行:牛的數量 N;

第 2..1+N 行:每行包含 2 個以空格分隔的正整數 X_i 和 ID_i;意義如題目描述;

【輸出格式】

輸出共一行,包含每個不同品種 ID 的照片的最低成本。

【輸入樣例】

6 25 7 26 1 15 1 22 3 20 1 30 1

【輸出樣例】

4

【輸入說明】

在不同的座標點 25,26,15,22,20,30 中有六頭牛

【輸出說明】

在約翰的牛中,從 X=22 到 X=26(整個規模為 4)包含了每個的不同品種的 ID 3,7 和 1。

【資料規模】

對於 50%的資料: 1≤N≤300;

對於 100%的資料:1≤N≤50,000;0≤X_i≤1,000,000,000;1≤ID_i≤1,000,000,000;

 

剛剛看到這一題,覺得不是很難,但是一做起來,就感覺束手無策,看了一下資料範圍,我感覺做法應該是O(n)或者是O(n^logn)

因為這一道題必須要排序,所以直接排除O(n)的做法

因為品種數量很大,所以要給di離散一下,可以省掉map

我想了想,決定用兩個指標,分別指向左右兩端,左指標for迴圈,右指標指向剛剛好能滿足條件的那個點,並且多次記錄最大值

查詢時間為O(n),加上快排O(nlogn)

也可能是我看到xi<=10^9的原因吧,我竟然用了一個二分,查詢時間是O(30n),因為30是常數所以時間還是O(N),

程式碼

#include<bits/stdc++.h>
using namespace std;
inline int read(){
    int x=0,f=0;char s=getchar();
    while(!isdigit(s))f|=s=='-',s=getchar();
    while( isdigit(s))x=(x<<1)+(x<<3)+s-48,s=getchar();
    return !f?x:-x;
}
const int N=5e4+20;
struct node{
    int x,d;
    inline bool operator<(const node &k)const{
        return x<k.x;
    }
}a[N];int n;
struct LSnode{
    int x,z,p;
    inline bool operator<(const LSnode &k)const{
        return x<k.x;
    }
}b[N];
int bk[N];
inline bool check(int x){
    memset(bk,0,sizeof(bk));
    int p2=1,ans=1;bk[a[1].d]=1;
    for(int p1=1;p1<=n;p1++){
        if(p2==n)break;	
        while(a[p2+1].x-a[p1].x<=x&&p2<n){
            p2++;bk[a[p2].d]++;
            if(bk[a[p2].d]==1)ans++;
        }
        if(ans==b[n].z)return 1;
        bk[a[p1].d]--;
        if(!bk[a[p1].d])ans--;
    }
    return 0;
}
int main(){
    n=read();
    for(int i=1;i<=n;i++){
        a[i].x=read();a[i].d=read();
        b[i].x=a[i].d;b[i].p=i;
    }
    sort(b+1,b+n+1);//離散化 
    b[1].z=1;
    for(int i=2;i<=n;i++)b[i].z=b[i-1].z+(b[i].x==b[i-1].x?0:1);
    for(int i=1;i<=n;i++)a[b[i].p].d=b[i].z;
    sort(a+1,a+n+1);
    int l=0,r=1e9,mid,ans=-1;//二分 
    while(l<=r){
        mid=(l+r)>>1;
        if(check(mid))ans=mid,r=mid-1;
        else l=mid+1;
    }
    printf("%d\n",ans);
    return 0;
}

打完二分以後突然發現裡面的check似乎可以直接用來求最小值,於是我又打了一個不用二分的程式

#include<bits/stdc++.h>
using namespace std;
inline int read(){
	int x=0,f=0;char s=getchar();
	while(!isdigit(s))f|=s=='-',s=getchar();
	while( isdigit(s))x=(x<<1)+(x<<3)+s-48,s=getchar();
	return !f?x:-x;
}
const int N=5e4+20;
struct node{
	int x,d;
	inline bool operator<(const node &k)const{
		return x<k.x;
	}
}a[N];int n;
struct LSnode{
	int x,z,p;
	inline bool operator<(const LSnode &k)const{
		return x<k.x;
	}
}b[N];
int bk[N];
int main(){
	n=read();
	for(int i=1;i<=n;i++){
		a[i].x=read();a[i].d=read();
		b[i].x=a[i].d;b[i].p=i;
	}
	sort(b+1,b+n+1);
	b[1].z=1;
	for(int i=2;i<=n;i++)b[i].z=b[i-1].z+(b[i].x==b[i-1].x?0:1);
	for(int i=1;i<=n;i++)a[b[i].p].d=b[i].z;
	sort(a+1,a+n+1);
	int p2=1,ans=1;bk[a[1].d]=1;
	int minn=1e9+10;
	for(int p1=1;p1<=n;p1++){
		while(p2<n&&ans<b[n].z){
			p2++;bk[a[p2].d]++;
			if(bk[a[p2].d]==1)ans++;
		}
		if(ans<b[n].z)break;
		minn=min(minn,a[p2].x-a[p1].x);
		bk[a[p1].d]--;
		if(!bk[a[p1].d])ans--;
	}
	printf("%d\n",minn);
	return 0;
}