HDU训练赛 洲哥的询问(主席树+分块)

洲哥的询问

Time Limit

6s

Memory Limit

262144KB

Judge Program

Standard

Ratio(Solve/Submit)

14.29%(2/14)

Description:

洲哥有n条线段,第i条线段可以用一个闭区间[li, ri]表示,现在他想让你回答q个问题。
对于每个问题,洲哥会给出m个点,他想知道在他拥有的n条线段中,有几条线段包含的点的个数为质数。
点用一个数x表示其位置。对于某个点x,如果li <= x <= ri,则第i条线段包含了点x。

Input:

第一行T,表示有T组数据。(1 <= T <= 10)
对于每组数据,第一行为n,表示洲哥拥有的线段条数。(1 <= n <= 5e4)
然后紧跟n行,第i行有两个数li, ri,用一个空格隔开。(1 <= li <= ri <= n)
接下来一行为一个数q,表示洲哥给定的q个询问。(1 <= q <= n)
然后紧跟的q行为q个询问。
在每个询问中,第一个数为m,表示该询问包含的点的个数。(1 <= m <= n)
然后紧跟着m个数x1, x2, x3, ......, xm,表示每个点的坐标(1 <= xi <= n)。
输入保证1 <= ∑m <= n

Output:

对于每组数据,输出q行,表示q个问题的答案。每组数据之间不用空行隔开。

Sample Input:

1
6
1 2
1 3
1 5
2 3
3 5
3 6
3
3 2 3 5
2 3 4
1 6

Sample Output:

5
3
0

Source:

hdu校赛

解析:

主席树的作用:
主席树根据每一条线段(排序后)进行可持久化插入右端点(新的树是在前面的树(左端点<i)基础上再插入左端点为i的线段的树)
本题通过区间的左右端点建树
这样第i棵树查询[r1,r2]的结果就是右端点在[r1,r2],左端点<=i的线段的数量。
查询时因为线段的连续性:在x从小到大排序后,包含x[2],x[8]的线段一定包含x[3..7]
这样一旦我们得到一个质数个数的区间[x[i],x[j]],那么左端点在(x[i-1],x[i]],右端点在[x[j],x[j+1])的线段都是满足的

得到左右端点的范围我们就可以将左端点范围作为根节点(注意这里因为主席树的前缀和性质只需要一次查询就够了,优化了查询),查询右端点符合条件的线段数目。

在本题中主席树处理的复杂度为m^2*logn
因此就因为这个复杂度里面包含m,所以就可以用它来处理m较小的情况。
较大的情况仍然用O(n)的复杂度处理
只要选取合适的边界,就可以使复杂度可控

O(n)的更新:根据线段来找,在[li,ri]的点的数量可以用点的前缀和来获得

这里取的临界是0.8*\sqrt{\frac{n}{ \ln {n}}}(这里分块对于小的一块是用临界来代替n,大的一块是n/临界——大的部分出现的最大次数,来代替出现次数的n)

这样总的时间复杂度是

\frac{n}{0.8*\sqrt{\frac{n}{ \ln {n}}}}*n+(0.8*\sqrt{\frac{n}{ \ln {n}}})^{2}*\log n

把常数拿掉的话总的复杂度就接近于1.25*n*\sqrt{n*\ln n}+0.64*n*\frac{\log n}{\ln n} \approx O(n\sqrt{n})

上面是题解给的临界,可以1s以内过,大概在0.7-0.8左右

我测试了\sqrt{n}的临界,需要1.2s左右,虽然时间复杂度算出来还小一点,

但是我感觉大概是因为临界变大了,跟多的样例进入到小的那部分(m^{2}*\log n),而小的那部分的复杂度比大的那部分(O(n))

还大,这样临界增大,小的那部分复杂度增大,并且进入的样例更多了,所以总的时间增加了。

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;

const int MAXN = 1e5+10;

typedef struct node
{
    int l,r;
}node;

node a[MAXN];
int rt[MAXN*60],ls[MAXN*60],rs[MAXN*60];
int sum[MAXN*60];
int x[MAXN];
int prime[MAXN],s[MAXN],tot;

bool cmp(node a,node b)
{
    if(a.l==b.l) return a.r<b.r;
    return a.l<b.l;
}


void build(int& root,int l,int r)
{

    root=++tot;

    sum[root]=0;

    if(l==r) return;

    int mid=(l+r)>>1;

    build(ls[root],l,mid);

    build(rs[root],mid+1,r);

}



void update(int& root,int l,int r,int last,int x)
{

    root=++tot;

    ls[root]=ls[last];

    rs[root]=rs[last];

    sum[root]=sum[last]+1;  //从上往下更新主席树的节点的值

    if(l==r) return ;

    int mid=(l+r)>>1;

    if(mid>=x) update(ls[root],l,mid,ls[last],x);

    else update(rs[root],mid+1,r,rs[last],x);

}



int query(int ss,int tt,int s,int e,int l,int r)
{

    if(s<=l&&r<=e)
    {
        return sum[tt]-sum[ss];
    }

    int mid=(l+r)>>1;
    int ans=0;
    if(mid>=s)
        ans+=query(ls[ss],ls[tt],s,e,l,mid);
    if(mid<e)   //!该区间是[mid+1,r]
        ans+=query(rs[ss],rs[tt],s,e,mid+1,r);

    return ans;
}



int main()
{
    int t;
    scanf("%d",&t);
    prime[0]=1;
    prime[1]=1;
    for(int i=2;i<MAXN;i++)
    {
        if(!prime[i])
        {
            for(int j=i+i;j<MAXN;j+=i)   //!
            {
                prime[j]=1;
            }
        }
    }
    while(t--)
    {
        tot=0;
        int n;
        scanf("%d",&n);
        for(int i=1;i<=n;i++)
            scanf("%d%d",&a[i].l,&a[i].r);

        sort(a+1,a+1+n,cmp);
        build(rt[0],1,n);
        for(int i=1,j=1;i<=n;i++)
        {
            rt[i]=rt[i-1];
            for(;j<=n&&a[j].l<=i;j++)
            {
                update(rt[i],1,n,rt[i],a[j].r);
            }
        }

        //int crisis=sqrt(n/log(n))*0.8;    //分块根据影响时间复杂度的量进行分块
        int crisis=sqrt(n);   //上面是题解的边界,这个是我自己试的边界
        int m,y;
        scanf("%d",&m);
        int ans=0;
        for(int i=1;i<=m;i++)
        {
            ans=0;
            scanf("%d",&y);
            for(int j=1;j<=y;j++)
                scanf("%d",x+j);
            sort(x+1,x+y+1);   //!!不然下面求前缀的时候会出错
            x[0]=0;x[y+1]=n+1;
            if(y>crisis)
            {
                s[0]=0;
                for(int j=1, k=1;j<=n;j++)
                {
                    s[j]=s[j-1];
                    for(;k<=y&&x[k]<=j;k++)
                    {
                        s[j]++;
                    }
                }
                for(int j=1;j<=n;j++)
                {
                    if(prime[s[a[j].r]-s[a[j].l-1]]==0)
                            ans++;
                }
            }
            else
            {
                //构造包含点的个数为质数个的区间
                for(int j=1;j<=y;j++)    //左端点
                {
                    for(int k=j+1;k<=y;k++)  //右端点
                    {
                        if(prime[k-j+1]==0)
                        {
                            ans+=query(rt[x[j-1]],rt[x[j]],x[k],x[k+1]-1,1,n);   //上面边界设置,左端点是(x[j-1],x[j]],右端点是[x[k],x[k+1])
                        }
                    }
                }
            }
            printf("%d\n",ans);
        }


    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_37025443/article/details/81092431