[COCI2011-2012#6] PASTELE
[COCI2011-2012#6] PASTELE
題意:
- 這份禮物共包含 \(n\) 支蠟筆。每隻蠟筆的顏色由色光三原色組成:紅、綠、藍。分別用引數 \(R_i,G_i,B_i\) 表示。這隻蠟筆的顏色就由這三個引數來決定。
- -對於兩支蠟筆 i,ji,j,我們定義它們之間的差異值為: \(\max(|R_i-R_j|,|G_i-G_j|,|B_i-B_j|)max(∣Ri−Rj∣,∣Gi−Gj∣,∣Bi−Bj∣)\)。定義一些蠟筆的色彩值為這些蠟筆中任意兩支蠟筆差異值的最大值。
- 給出這 \(n\) 支蠟筆的特徵值,請找出 \(k\) 支蠟筆,使得色彩值最小。
第一眼看到這一題可能並沒有什麼思路。我們注意到 \(R_i,G_i,B_i\)
通過二分我們可以得到一個更優秀的複雜度:
\[O(255\times255\times255\times n \times log(255)) \] 但是這個複雜度明顯過不了這個題目。我們得進一步優化。聯想到之前解決這種單個元素具有多個屬性的題目的解法,我們可以通過排序
首先 \(n\) 個彩筆按照 \(R\) 的數值進行排序,那麼我們在列舉時只需要列舉 \(G\),\(B\) 的下界,而在檢驗答案時,我們從左到右將符合條件的元素新增進一個佇列裡,由於我們已經通過排序使 \(n\) 個彩筆的 \(R\) 值有序,那麼隊尾的元素的 \(R\) 值一定是最大的,而隊首的元素的 \(R\) 值一定是最小的。我們每新增進一個符合條件的元素在隊尾,如果隊首與隊尾元素的 \(R\) 值的差大於我們列舉的答案 \(x\) ,那麼就不斷彈出隊首直至隊首與隊尾元素的 \(R\) 值的差小於我們列舉的答案 \(x\) ,然後統計當前佇列內元素的個數,如果個數大於 \(k\)
此時我們可以拿到 60pts ,程式碼如下:
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1e5+5;
int n,k1;
struct pen
{
int val[4];
bool operator < (const pen &x)const
{
return val[1]<x.val[1];
}
}a[MAXN];
queue <int> q;
bool judge(int k,int i,int j,int x)
{
if(a[k].val[2]>=i&&a[k].val[2]<=i+x&&a[k].val[3]>=j&&a[k].val[3]<=j+x) return true;
return false;
}
bool check(int x)
{
int ans=0;
for(int i=0;i<=255;++i)
{
for(int j=0;j<=255;++j)
{
int cnt=0;
while(!q.empty()) q.pop();
for(int k=1;k<=n;++k)
{
if(q.empty()&&judge(k,i,j,x))
{
++cnt;
q.push(k);
}
else if(judge(k,i,j,x))
{
q.push(k);
while(a[k].val[1]-a[q.front()].val[1]>x) q.pop(),--cnt;
++cnt;
}
ans=max(ans,cnt);
}
}
}
return ans>=k1;
}
void print(int x)
{
for(int i=0;i<=255;++i)
{
for(int j=0;j<=255;++j)
{
int cnt=0;
while(!q.empty()) q.pop();
for(int k=1;k<=n;++k)
{
if(q.empty()&&judge(k,i,j,x))
{
++cnt;
q.push(k);
}
else if(judge(k,i,j,x))
{
q.push(k);
while(a[k].val[1]-a[q.front()].val[1]>x) --cnt,q.pop();
++cnt;
}
if(cnt>=k1)//再做一遍得到符合題意的三個屬性上下界。
{
cnt=k1;
while(cnt--)
{
printf("%d %d %d\n",a[q.front()].val[1],a[q.front()].val[2],a[q.front()].val[3]);//輸出佇列裡的元素。
q.pop();
}
return ;
}
}
}
}
}
int main()
{
scanf("%d %d",&n,&k1);
for(int i=1;i<=n;++i)
scanf("%d %d %d",&a[i].val[1],&a[i].val[2],&a[i].val[3]);
sort(a+1,a+1+n);
int l=1,r=255,ans=255;
while(l<=r)
{
int mid=l+r>>1;
if(check(mid))
{
ans=mid;
r=mid-1;
}
else l=mid+1;
}
printf("%d\n",ans);
print(ans);
return 0;
}
顯然, \(n\) 的數值太大了,我們必須想辦法將 \(n\) 優化掉。我們可以發現我們 check 函式裡面實際上重複將 \(n\) 個元素裝進佇列裡多次。那麼我們嘗試可以將元素塞入 \(\text{vector}\) 中。同樣的,
我們以 \(R\) 值排序,排序後,再一個個塞入以 \(G\),\(B\) 的值為下標的 \(\text{vector}\) 中。在 check 中我們同樣列舉三個屬性的下界,並通過二分出的答案得到三個屬性的上界。我們可以先列舉 \(R\) 的上下界,然後接下來兩個屬性我們已經將他們當做下標,所以想象一個二維平面,對於一個點 \((x,y)\) 我們賦予它一個權值 \(w\) 。\(w\) 為 \(R\) 值在列舉的上下界範圍內,\(G\) 為 \(x\),\(B\) 為 \(y\) 的元素個數。這個我們可以在 \(\text{vector}[x][y]\) 二分上下界的位置並相減得到元素個數,但這樣效率太低。我們考慮從小到大列舉 \(R\) 值的下界,由於 \(\text{vector}\) 中的元素 \(R\) 值是有序的,我們用一個佇列來維護 \(\text{vector}[x][y]\) 中符合上下界元素的個數。具體可以見程式碼。
這樣操作後我們會發現,我們需要判斷是否有一個 \(x\times x\) 大小的矩陣中所有點的權值 \(w\) 之和大於等於 \(k\)。我們可以用二維字首和來解決這樣的問題。
同樣的,上文中的方案是再算出符合答案的三個屬性的上下界進行輸出,這樣有些慢,我們考慮開一個數組記錄每次二分 check 時得到的三個屬性的上下界,而在輸出方案時我們遍歷 \(n\) 個彩筆,符合條件的就輸出。相當於我們使用了一個以空間來換時間的策略。
此時,我們將複雜度優化為
\[O(255\times255\times255\times \log(255)+\;n\;+\:n\times \log(n)) \] 這個複雜度可以通過這道題目,程式碼如下:
#include<bits/stdc++.h>
#define R register
using namespace std;
const int MAXN = 1e5+5;
int n,k1,lim;
struct pen
{
int val[4];
}a[MAXN];
vector <int> vec[256][256];
int cnt[256][256],head[256][256],tail[256][256],ans[256][3],sum[256][256];
inline int read(){
int s=0,w=1;
char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
while(ch>='0'&&ch<='9') s=s*10+ch-'0',ch=getchar();
return s*w;
}
bool check(int x)
{
memset(head,0,sizeof head);
memset(tail,0,sizeof tail);
for(R int i=0;i<=lim;++i)
{
for(R int j=0;j<=lim;++j)
{
for(R int k=0;k<=lim;++k)
{
while(head[j][k]<vec[j][k].size()&&vec[j][k][head[j][k]]<i) ++head[j][k];
while(tail[j][k]<vec[j][k].size()&&vec[j][k][tail[j][k]]<=i+x) ++tail[j][k];//佇列維護。
cnt[j][k]=tail[j][k]-head[j][k];
bool flag=0;
sum[j][k]=(j?sum[j-1][k]:0)+(k?sum[j][k-1]:0)+cnt[j][k]-(j&&k?sum[j-1][k-1]:0);//二維字首和。
if(j>x&&k>x)
if(sum[j][k]-sum[j-x-1][k]-sum[j][k-x-1]+sum[j-x-1][k-x-1]>=k1) flag=true;
else if(j>x&&k<=x)
if(sum[j][k]-sum[j-x-1][k]>=k1) flag=true;
else if(j<=x&&k>x)
if(sum[j][k]-sum[j][k-x-1]>=k1) flag=true;
else if(j<=x&&k<=x)
if(sum[j][k]>=k1) flag=true;
if(flag)
{
ans[x][0]=i,ans[x][1]=j,ans[x][2]=k;//記錄此時三個屬性的上下界。
return true;
}
}
}
}
return false;
}
inline int Max(int a,int b,int c,int d)
{
return max(max(a,b),max(c,d));
}
void print(int x)
{
int a1,b,c;
a1=ans[x][0],b=ans[x][1],c=ans[x][2];
int cnt=0;
for(R int i=1;i<=n;++i)
{
if(a[i].val[1]>=a1&&a[i].val[1]<=x+a1)
{
if(a[i].val[2]>=b-x&&a[i].val[2]<=b)
{
if(a[i].val[3]>=c-x&&a[i].val[3]<=c)
{
++cnt;
printf("%d %d %d\n",a[i].val[1],a[i].val[2],a[i].val[3]);
if(cnt==k1) return ;
}
}
}
}
}
bool cmp(pen a,pen b)
{
return a.val[1]<b.val[1];
}
int main()
{
n=read(),k1=read();
for(R int i=1;i<=n;++i)
{
a[i].val[1]=read();
a[i].val[2]=read();
a[i].val[3]=read();
}
sort(a+1,a+1+n,cmp);
lim=255;
for(R int i=1;i<=n;++i)
vec[a[i].val[2]][a[i].val[3]].push_back(a[i].val[1]);
int l=1,r=lim,ans=lim;
while(l<=r)
{
int mid=l+r>>1;
if(check(mid))
{
ans=mid;
r=mid-1;
}
else l=mid+1;
}
printf("%d\n",ans);
print(ans);
return 0;
}
這樣我們就通過了這道題。其實我們可以優化掉 \(\text{vector}\) 採取三維字首和的方式。check 就查詢有沒有一個邊長為 \(x\) 的立方體中所有點的權值之和大於 \(k\) 。這種方法這裡就不在贅述了。(其實是我也不會)