dtoi1363 楼房重建 (rebuild)

题意:

     有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\n",getans());
    }
    return 0;
}
posted @ 2020-01-28 23:19  1124828077ccj  阅读(112)  评论(0编辑  收藏  举报