分梨子
時間限制: 1 Sec 內存限制: 64 MB
題目描述
Finley家的院子裏有棵梨樹,最近收穫了許多梨子。於是,Finley決定挑出一些梨子,分給幼稚園的寶寶們。可是梨子大小味道都不太一樣,一定要儘量挑選那些差不多的梨子分給孩子們,那些分到小梨子的寶寶纔不會哭鬧。 每個梨子都具有兩個屬性值,Ai和Bi,本別表示梨子的大小和甜度情況。假設在選出的梨子中,兩個屬性的最小值分別是A0和B0。只要對於所有被選出的梨子i,都滿足C1*(Ai-A0)+C2*(Bi-B0)≤C3(其中,C1、C2和C3都是已知的常數),就可以認爲這些梨子是相差不多的,可以用來分給小朋友們。 那麼,作爲幼稚園園長的你,能算出最多可以挑選出多少個梨子嗎?
輸入
第一行一個整數N(1≤N≤2000),表示梨子的總個數。 第二行三個正整數,依次爲C1,C2和C3(C1,C2≤2000,C3≤10^9)。 接下來的N行,每行兩個整數。第i行的兩個整數依次爲Ai和Bi。
輸出
只有一個整數,表示最多可以選出的梨子個數。
樣例輸入
3
2 3 6
3 2
1 1
2 1
樣例輸出
2
解題報告:
這不是一道dp題
對此題的第一想法,n^3暴力
但是很明顯這會超時
接下來,你要擴寬你的思維,開始解題(亂搞)
注意到題目中的
對於所有被選出的梨子i,都滿足
C1∗(Ai−A0)+C2∗(Bi−B0)≤C3
如果在這裏把A看做平面直角座標系的x軸,B看做y軸,
每一個梨子看做平面直角座標系中的一個點
設Ai-A0=x, Bi-B0=y
極限時不等式左右兩邊相等
那麼有
推導可得
它相當於座標系中的一條直線
可以看出,能與梨子(A0,B0)組成一組的梨子不能在這條直線上方
同時,由於題目中定義A0爲選中一組梨子A的最小值,B0爲B的最小值
所以任意一個選中的梨子只能在直線x=A0的右方和直線y=B0的上方
這些直線圍城了一個三角形:
只有在這個三角形內(或邊上)的梨子才能被選中
三角形的三點互相相對位移是一定的
如果O(n^2)枚舉三角形的左下角定點(A0,B0)
(設i點有着組內最小A,j點有着組內最小B,枚舉i,j爲O(n^2))
那麼問題變成了:如何快速求出三角形內的點的數量?
首先見下圖:
S陰影=S總-S1-S2+S3
其中S總表示塗了顏色的面積(請忽略圖中的S4)。
1.S總
如果要快速求出S總,那麼在順序枚舉(A0,B0)時要保證三角形的斜邊一次比一次靠左/靠下。最初時,所有點都會在S陰影中,每一次移動直線時只需從S中以O(k)時間刪去k個移動後處於白色區域的點就可以了
具體做法,用O(n^2)時間複雜度找出所有合法(A0,B0)組合(當i有着A0,j有着B0時Ai小於Aj且Bj小於Bi),存入一個數組內,稱爲虛擬點
對該數組按照每個虛擬點代表的向量在向量(C1,C2)上的投影模長從大到小排序,接下來順序枚舉,就可以保證直線的移動順序(此處不多解釋)。
把所有的代表梨子的點也按照以上規則進行排序,壓入隊列,每次移動直線時只需從隊頭連續刪除k個節點就能保證剩下節點全部在S總之內了。
2.S1和S2
注意到題目中Ai,Bi<=2000,於是使用兩個大小爲2000的樹狀數組維護前綴和,每次從S總隊列刪除節點時把節點也從樹狀數組中刪去即可,
查詢,修改均爲logL
3.S3
S3並不需要動態維護(不多解釋)
設f[i][j]爲在矩形(1,1)-(i,j)中的代表梨子的節點數量
顯然有
實現時把(i,j)有梨子的f[i][j]=1
然後f[i][j]+=f[i-1][j]+f[i][j-1]-f[i-1][j-1]
最後取S陰影的最大值輸出即可得到答案
(腦洞夠大啊= =)
AC代碼
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
const int EPS=5000;
const int MAXN=2100;
const int MAXL=2010;
inline int max(const int &a,const int &b)
{return a>b?a:b;}
int pear[MAXN][2];
double stdx,stdy,sq;
struct mpoint{
int x,y;
double sh;
inline void cal(){
sh=((stdx*x+stdy*y)/sq)*EPS;
}
}points[(MAXN-400)*(MAXN-400)],pv[MAXL];
bool operator<(const mpoint&a,const mpoint&b){
return a.sh>b.sh;
}
int pt;
int cx[MAXL],cy[MAXL];
int atm;
inline int lowbit(const int &a)
{return a&-a;}
inline int query(int *c,int p){
int ret=0;
while(p){
ret+=c[p];
p-=lowbit(p);
}
return ret;
}
inline void add(int *c,int p,const int &v){
while(p<=atm){
c[p]+=v;
p+=lowbit(p);
}
}
int rect[MAXL][MAXL];
int main(){
int n;
scanf("%d",&n);
atm=n;
int c1,c2,c3;
int mxa=0,mxb=0;
scanf("%d%d%d",&c1,&c2,&c3);
stdx=c1;
stdy=c2;
sq=sqrt(c1*c1+c2*c2);
for(int i=1;i<=n;i++){
scanf("%d%d",&pear[i][0],&pear[i][1]);
mxa=max(mxa,pear[i][0]);
mxb=max(mxb,pear[i][1]);
rect[pear[i][0]][pear[i][1]]++;
pv[i].x=pear[i][0];
pv[i].y=pear[i][1];
pv[i].cal();
}
for(int i=1;i<=mxa;i++)//初始化rect
for(int j=1;j<=mxb;j++){
rect[i][j]+=
rect[i-1][j]+rect[i][j-1]
-rect[i-1][j-1];
}
for(int i=1;i<=n;i++)//初始化可能的向量
for(int j=1;j<=n;j++){
if(pear[i][1]<pear[j][1]||
pear[j][0]<pear[i][0])//這兩個點不兼容
continue;
points[++pt].x=pear[i][0];
points[pt].y=pear[j][1];
points[pt].cal();
}
std::sort(points+1,points+(pt+1));//按在y=x投影的模長排序
std::sort(pv+1,pv+(n+1));
for(int i=1;i<=n;i++){//初始化樹狀數組
add(cx,pv[i].x,1);
add(cy,pv[i].y,1);
}
double lk=-(double)c1/c2;
double lb=(double)c3/c2;
int ans=0,up=1;
for(int i=1;i<=pt;i++){
while((lk*(pv[up].x-points[i].x)+lb)
<(pv[up].y-points[i].y)
){
add(cx,pv[up].x,-1);
add(cy,pv[up].y,-1);
up++;
}
ans=max(ans,n-up+1//總點數
-query(cx,points[i].x-1)//左邊
-query(cy,points[i].y-1)//下面
+rect[points[i].x-1]
[points[i].y-1]);//補上一個矩形
}
printf("%d\n",ans);
}
接下來是廢話
更神奇的是這題被分到了dp作業裏,
於是一直猛想轉移方程,(當然想不出來= =)
也搜不到題解
無奈之下
在知道上提問
結果遇見大神(雖然不全對,但是幫忙打開了腦洞)
然後開始亂搞實現,解決各種問題
然後成功成爲第一個學校oj上ac的人
寫博客希望能給其他搜不到題解的人一點頭緒
亂搞出奇跡系列