2021杭电多校第一场1010.zoto

2021杭电多校第一场1010.zoto

原题地址:Problem - 6959 (hdu.edu.cn)

题目大意:给出一些整数点 ( i , f [ i ] ) ( 1 ≤ i ≤ n ) (i,f[i])(1\leq i \leq n) (i,f[i])(1in), q q q次询问,每次询问一个矩形范围有多少个y轴坐标不同的点。

1 ≤ n , q ≤ 1 0 5 1 \leq n,q \leq10^5 1n,q105

问题转化:

​ 该问题可以转化为:

​ 有一个长度为n的序列,q次询问,每次询问 [ l , r ] [l,r] [l,r]区间内数值在 [ a , b ] [a,b] [a,b]之间的不同数的个数。

该问题的做法和 这篇博客论述的 几乎一模一样,只是询问变得更简单了。

容易证明这种莫队加分块的做法复杂度为:
O ( n ∗ n ) O(n*\sqrt n) O(nn )

莫队分块AC代码:

#include <bits/stdc++.h>
#define debug(x) cout<<#x"=" <<x<<'\n';
using ll=long long;
using namespace std;
int block;///块的大小,记得计算
const int M=1e5+5;
struct BLOCK
{
    int sum[M],a[M];
    void add(const int &x,const int &u) ///在x的位置加上y
    {
        sum[x/block]+=u;
        a[x]+=u;
    }
    int ask(const int &l,const int &r)///询问[l,r]的和
    {

        int ans=0;
        if(r-l<=block)
        {
            for(int i=l;i<=r;i++) ans+=a[i];
            return ans;
        }
        int bl=(l/block+1)*block;
        int br=r/block*block;
        for(int i=l;i<bl;i++) ans+=a[i];
        for(int i=br;i<=r;i++) ans+=a[i];
        int blockl=l/block+1;
        int blockr=r/block-1;
        for(int i=blockl;i<=blockr;i++) ans+=sum[i];
        return ans;
    }
    void clear()
    {
        memset(a,0,sizeof(a));
        memset(sum,0,sizeof(sum));
    }
}B;
struct node
{
    int l,r,id,a,b;
    bool operator < (const node &cmp) const
    {
        if(l/block == cmp.l/block)  return r < cmp.r;
        return l/block < cmp.l/block;
    }

} q[M];

int num[M],a[M],hs[M];
void add(int x)
{
    x=a[x];
    if(hs[x]==0) B.add(x,1);
        hs[x]++;

}
void del(int x)
{
    x=a[x];
    if(hs[x]==1) B.add(x,-1);
    hs[x]--;
}

int main()
{

    int T;
    scanf("%d",&T);
    while(T--)
    {
        memset(hs,0,sizeof(hs));
        B.clear();
        int n,m;
        scanf("%d%d",&n,&m);
        block=sqrt(n);
        for(int i=1;i<=n;i++) scanf("%d",&a[i]);
        for(int i=1;i<=m;i++)
        {
            scanf("%d%d%d%d",&q[i].l,&q[i].a,&q[i].r,&q[i].b);
            q[i].id=i;
        }
        sort(q+1,q+m+1);
        int l = 1,r = 0;
        for(int i = 1; i <= m; i ++)
        {

            while(r < q[i].r) add(++r);
            while(r > q[i].r) del(r--);
            while(l < q[i].l) del(l++);
            while(l > q[i].l) add(--l);
            num[q[i].id]=B.ask(q[i].a,q[i].b);
        }
        for(int i = 1; i <= m; i ++) printf("%d\n",num[i]);
    }
    return 0;
}


思维历程:

对于该题我想到了一种比较有意思的做法:

从左到右建立线段树,线段树开在值域上,插入的是下标,每个节点都记录一下。

接下来用样例来解释建树过程:

1

4 2

1 0 3 1

1 0 4 3(询问 [ 1 , 4 ] [1,4] [1,4]中有多少个值在 [ 0 , 3 ] [0,3] [0,3]中)

1 0 4 2

序列是 1 0 3 1

先将(1,1)插入到树中:(第一个值为值,第二个为下标)

在这里插入图片描述

接下来插入(0,2)

在这里插入图片描述

接下来插入(3,3)

在这里插入图片描述

接下来应该插入(1,4),但是由于1的位置以已经有值了,所以要先删掉(保证无重)

在这里插入图片描述

然后再插入(1,4)

在这里插入图片描述

提前将询问按照r进行记录,当插入 ( a [ r i ] , r i ) (a[r_i],r_i) (a[ri],ri)之后就进行就进行询问,由于已经插入的下标是在 [ 1 , r i ] [1,r_i] [1,ri]范围内,所以我们只需要关心值域和和 l i l_i li就行。

实际上就是询问,[a,b]所包含的这些节点上下标大于等于 l i l_i li的有多少个

向下递归时,只要一个节点所表示的值域完全被询问的值域所覆盖就可以直接返回结果。(和普通线段树的区间求和一样)

对于该线段树的节点需要一个数据结构S

要能过支持n次增加和删除一个数,n次询问S中有多少个大于等于x的数,并且空间上要能开的下 n ∗ log ⁡ n n*\log n nlogn个S,所有S的元素总和为 n ∗ log ⁡ n n*\log n nlogn

S选取动态开点的权值线段树

那么增删查的复杂度都是log级别

总的复杂度为
O ( n ∗ log ⁡ n ∗ l o g n ) O(n*\log n* log n) Onlognlogn
可惜的是,这样做的常数略大,无法通过该题,实际测试的时间约为上个做法的两倍。

常数原因分析如下:

  • 对于每个节点有可能要执行一次删除和一次添加
  • 莫队分块做法就是跑循环累加,几乎没有常数,跑的很快,而这种做法是很多层的递归,内含很多判断和计算。

值得一提的是该做法的空间复杂度也是

O( n ∗ log ⁡ n ∗ log ⁡ n n*\log n*\log n nlognlogn

树套树代码:

#include <bits/stdc++.h>
#define debug(x) cout << #x "=" << x << '\n';
using ll = long long;
using namespace std;
const int M = 1e5 + 10;
struct query
{
    int l, r, a, b, id;
};

vector<query> Ask[M];
int a[M];
struct Segment_tree
{
    struct node
    {
        int l, r, data; //l r表示左儿子和右儿子
    };
    static node t[M * 200];
    static int tot;
    const int lM, rM; //值域
    int root;         //根节点编号
    Segment_tree(int _lM = 0, int _rM = M) : lM(_lM), rM(_rM)
    {
        root = ++tot;
    }
    void add_dfs(int x, int v, int p, int l, int r) //在x的位置上加v,当前节点是p
    {
        if (l == r)
        {
            t[p].data += v;
            return;
        }
        int mid = (l + r) >> 1;
        if (x <= mid)
        {
            if (t[p].l == 0)
                t[p].l = ++tot;
            add_dfs(x, v, t[p].l, l, mid);
        }
        else
        {
            if (t[p].r == 0)
                t[p].r = ++tot;
            add_dfs(x, v, t[p].r, mid + 1, r);
        }
        t[p].data = t[t[p].l].data + t[t[p].r].data;
    }
    void add(int x, int v)
    {
        add_dfs(x, v, root, lM, rM);
    }
    ll ask_dfs(int x, int p, int l, int r)
    {
        if (l >= x)
        {
            return t[p].data;
        }
        if (r < x)
        {
            return 0;
        }
        int mid = (l + r) >> 1;
        ll ans = 0LL;
        if (t[p].l)
            ans += ask_dfs(x, t[p].l, l, mid);
        if (t[p].r)
            ans += ask_dfs(x, t[p].r, mid + 1, r);
        return ans;
    }
    ll ask(int x) ///询问大于等于x的有多少个
    {
        return ask_dfs(x, root, lM, rM);
    }
};
int Segment_tree::tot = 0;
Segment_tree::node Segment_tree::t[M * 200] = {};
//static 初始化

struct tree
{
    int l, r;
    Segment_tree data;
} t[4 * M];
void build(int p, int l, int r)
{
    t[p].r = r, t[p].l = l;
    if (r == l)
    {
        return;
    }
    int mid = (r + l) / 2;
    build(2 * p, l, mid);
    build(2 * p + 1, mid + 1, r);
}
ll ask(int p, int x, int l, int r)
{
    if (t[p].l >= l && t[p].r <= r)
    {
        return t[p].data.ask(x);
    }
    int mid = (t[p].l + t[p].r) / 2;
    ll ans = 0;
    if (l <= mid)
        ans += ask(2 * p, x, l, r);
    if (r > mid)
        ans += ask(2 * p + 1, x, l, r);
    return ans;
}
void changeone(int p, int x, int v, int f)
{
    t[p].data.add(v, f);
    if (t[p].l == t[p].r)
    {
        return;
    }
    int mid = (t[p].l + t[p].r) / 2;
    if (x <= mid)
        changeone(2 * p, x, v, f);
    else
        changeone(2 * p + 1, x, v, f);
}
int hs[M];
int ans[M];
int main()
{
    //freopen("1.in","r",stdin);
    //freopen("1.out","w",stdout);
    //cout << sqrt(1e5);
    build(1, 0, M);
    int T;
    scanf("%d", &T);
    while (T--)
    {
        for (int i = 0; i < M; i++)
            Ask[i].clear();
        // memset(hs,0,sizeof(hs));
        // for(int i=0; i<=Segment_tree::tot; i++)
        // {
        //     Segment_tree::t[i].data=0;
        //     Segment_tree::t[i].l=0;
        //     Segment_tree::t[i].r=0;
        // }
        for (int i = 0; i < M; i++)
        {
            if (hs[i])
                changeone(1, i, hs[i], -1);
            hs[i] = 0;
        }
        //cout << Segment_tree ::tot << '\n';
        //Segment_tree::tot = 4 * M;
        int n, m;
        scanf("%d%d", &n, &m);
        for (int i = 1; i <= n; i++)
            scanf("%d", &a[i]);
        for (int i = 1; i <= m; i++)
        {
            int l, r, a, b;
            scanf("%d%d%d%d", &l, &a, &r, &b);
            Ask[r].push_back({l, r, a, b, i});
        }
        for (int i = 1; i <= n; i++)
        {
            if (hs[a[i]])
            {
                changeone(1, a[i], hs[a[i]], -1);
            }
            changeone(1, a[i], i, 1);
            hs[a[i]] = i;
            for (auto it : Ask[i])
            {
                ans[it.id] = ask(1, it.l, it.a, it.b);
            }
        }
        for (int i = 1; i <= m; i++)
        {
            printf("%d\n", ans[i]);
        }
    }
    return 0;
}

/*
1
10 1
1 6 8 7 2 1 4 1 4 8
3 1 9 2
*/
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值