题意:
有n个位置上可以盖房子,一开始每个位置都是空的,每次会让你将位置x的房子的高度变成y,询问从0可以看到多少栋房子,x,y给定。如果这栋楼房上任何一个高度大于0的点与(0,0)的连线没有与之前的线段相交,那么这栋楼房就被认为是可见的。
题解:
如果将一个房子的最高点和(0,0)点的连线的斜率作为每个点的权值,那么答案就是贪心上升子序列的长度。贪心上升子序列定义为:一开始队列为空,每次从1开始,遇到一个值比队尾大就把它加入队尾。(与最长上升子序列不同)
此题可以使用线段树,然而我并不知道怎么做,所以我使用了分块。
对于每一块中单独求一次贪心上升子序列。然后求答案的时候,对于每一块,只需要把当前队列里最后一个元素取出来,在当前块的贪心上升子序列中找到第一个比这个元素大的位置,就可以计算这一块的贡献了。不过需要二分查找,效率为O(n*块数*log(n))。(当我写到这里时,我意识到并不需要二分,可以去掉一个log,但我懒得改了)。不过这效率实在是低,所以多取几个块大小试几次就可以过了。
#include<cstdio> #include<cmath> #include<algorithm> #include<cstdlib> using namespace std; int n,m,fk,cnt,g[3002][3002],len[10000]; double h[100002]; int ccj(int num,int x,int y,int t){ while(x<y) { int mid=(x+y)/2; if (h[g[num][mid]]/g[num][mid]>h[t]/t)y=mid;else x=mid+1; } return x; } int getans(){ int x=1,ans=1; for (int i=0;i<=cnt;i++) { int wz=ccj(i,0,len[i],x),sum; sum=len[i]-wz; if (sum) { x=g[i][len[i]-1];ans+=sum; } } if (!h[1])ans--; return ans; } int main() { scanf("%d%d",&n,&m); fk=600; for (int i=1;i<=n;i++) { cnt=i/fk; if (!len[i/fk])g[i/fk][len[i/fk]++]=i; } for (int i=1;i<=m;i++) { int x,y; scanf("%d%d",&x,&y); h[x]=y;len[x/fk]=0;int t=x/fk; for (int i=max(x/fk*fk,1);i<(x/fk+1)*fk&&i<=n;i++) if (!len[t] || h[i]/i>h[g[t][len[t]-1]]/g[t][len[t]-1])g[t][len[t]++]=i; printf("%d ",getans()); } return 0; }