POJ2528 Mayor‘s posters

本文讲解了如何利用线段树和离散化技术解决正整数区间值域中不同值个数的问题。通过动态开点优化查询效率,适用于大规模数据和区间数量庞大的场景。提供了离散化代码实例,展示了如何将区间操作转换为离散化的查询,大幅提升了查询性能。
摘要由CSDN通过智能技术生成

知识点:(动态开点)线段树、离散化

题目链接

题意

一段正整数值域(每个位置初始为无值)可执行一个操作:
[ l , r ] [l,r] [l,r]覆盖一个新值。
按顺序给定若干区间,求值域中不同值的个数。

思路

既要区间修改,又要区间查询,考虑线段树。更新 O ( q l o g n ) O(qlogn) O(qlogn),查询 O ( n l o g n ) O(nlogn) O(nlogn)
正整数值域过大,区间数又在 1 e 5 1e5 1e5范围内,考虑离散化。
查询区间固定为离散化后的值域,不需要维护子区间的信息,所以不需要上推操作,查询改为单点查询即可。
并附上很久之前学习动态开点的代码:

代码(离散化)

#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
#define root tree[rt]
#define sonl tree[rt<<1]
#define sonr tree[rt<<1|1]
using namespace std;
const ll N=1e4+10;

ll n;
ll arr[2][N];
//注意数组范围
ll lisan[N*2];//左右区间拆开进行离散化
ll newarr[2][N];//离散化后的区间
struct node {
  ll l,r,val,lazy;//val:区间序号,lazy:子区间序号
} tree[(N*2)<<2];

void pushdown(ll rt) {
  if(root.lazy==0) return;
  //虽然不需要子区间信息,但是要把懒惰标记推到叶节点,
  //这句赋值只对叶节点有用
  sonl.val=sonr.val=root.lazy;
  sonl.lazy=sonr.lazy=root.lazy;
  root.lazy=0;
}

void build(ll rt,ll l,ll r) {
  root.l=l;
  root.r=r;
  if(l==r) {
//    root.val=-1;
    root.val=root.lazy=0;
    return ;
  }
  ll mid=l+r>>1;
  build(rt<<1,l,mid);
  build(rt<<1|1,mid+1,r);
}

void update(ll rt,ll l,ll r,ll val) {
  if(l<=root.l&&r>=root.r) {
    root.val=val;
    root.lazy=val;
    return ;
  }
  pushdown(rt);
  ll mid=root.l+root.r>>1;
  if(l<=mid) update(rt<<1,l,r,val);
  if(r>mid) update(rt<<1|1,l,r,val);
}

bool cnt[N*2];//序号i是否存在

void query(ll rt,ll l,ll r) {
  if(root.l==root.r) {
    cnt[root.val]=true;
    return;
  }
  pushdown(rt);
  query(rt<<1,l,r);
  query(rt<<1|1,l,r);
}

void init() {
  memset(cnt,0,sizeof cnt);
}

void solve() {
  scanf("%lld",&n);
  init();
  for(ll i=0; i<n; i++) {
    scanf("%lld%lld",&arr[0][i],&arr[1][i]);
    //拆分
    lisan[i*2]=arr[0][i];
    lisan[i*2+1]=arr[1][i];
  }
  //注意n的范围
  //离散化
  sort(lisan,lisan+n*2);
  ll *len=unique(lisan,lisan+n*2);
  for(ll i=0; i<n; i++) {
    newarr[0][i]=lower_bound(lisan,len,arr[0][i])-lisan+1;
    newarr[1][i]=lower_bound(lisan,len,arr[1][i])-lisan+1;
  }
  //建树
  build(1,1,n*2);
  for(ll i=0; i<n; i++) update(1,newarr[0][i],newarr[1][i],i+1);
  //查询
  n*=2;
  query(1,1,n);
  ll ans=0;
  for(ll i=1; i<=n+2; i++) ans+=cnt[i];
  printf("%lld\n",ans);
}

int main() {
  ll t;
  scanf("%lld",&t);
  while(t--)
    solve();
  return 0;
}

代码(动态开点)

码风差异略大

#include"vector"
#include"iostream"
#include"bitset"
#include"iomanip"
#define ll long long
#define db(x) cout<<fixed<<setprecision(14)<<#x<<':'<<(x)<<endl
#define pb push_back
#define mk make_pair
#define x first
#define y second
#define max(a,b) (a<b?b:a)
#define mix(a,b) (a<b?a:b)
#define mid (l+r>>1)
//注意rt为引用参数
#define tinfo ll &rt,ll l,ll r
#define sl sonl[rt],l,mid
#define sr sonr[rt],mid+1,r
#define tdata rt,1,inf
#define vec vector<ll>
using namespace std;
const ll N=1e4+10,inf=1e7;

vec sonl,sonr,lzy;//sonl[i]:i节点左子树的根节点,sonr同理,lzy与上同
//cnt:答案,rt:仅为迎合宏定义而设置的变量,请代具体值,logn:动态开点的最大范围
ll t,n,cnt,rt,logn=64-__builtin_clzll(inf);
bitset<N> vis;//与上cnt同

//这棵树实际上只维护了lazy,val始终为空(甚至没有开辟数组)
void pushdown(ll rt) {
  if(!lzy[rt]) return;
  if(!sonl[rt]) sonl[rt]=++cnt;
  if(!sonr[rt]) sonr[rt]=++cnt;
  lzy[sonl[rt]]=lzy[sonr[rt]]=lzy[rt];
  lzy[rt]=0;
}

void update(tinfo,ll tl,ll tr,ll w) {
  if(!rt) rt=++cnt;
//  db(rt);
  if(tl<=l&&tr>=r) {
    lzy[rt]=w;
    return;
  }
  pushdown(rt);
  if(tl<=mid) update(sl,tl,tr,w);
  if(tr>mid) update(sr,tl,tr,w);
}

ll query(tinfo,ll tl,ll tr) {
  if(!rt) return 0;
  if(lzy[rt]) {
  	//未出现过,返回1个,否则返回0个
    if(vis[lzy[rt]]) return 0;
    vis[lzy[rt]]=true;
    return 1;
  }
  ll ans=0;
  if(tl<=mid) ans+=query(sl,tl,tr);
  if(tr>mid) ans+=query(sr,tl,tr);
  return ans;
}

void init(ll n) {
  rt=0;
  vis.reset();
//  db(n*logn);
  //每开一个点会多产生一个左子树节点和一个右子树节点(大概)
  sonl.assign(3*n*logn+10,0);
  sonr.assign(3*n*logn+10,0);
  lzy.assign(3*n*logn+10,0);
  cnt=0;
}

int main() {
  ios::sync_with_stdio(false);
  cin.tie(0);
  cout.tie(0);

  cin>>t;
  while(t--) {
    cin>>n;
    init(n);
    for(ll i=1; i<=n; ++i) {
      ll l,r;
      cin>>l>>r;
      update(tdata,l,r,i);
    }
    rt=1;
    cout<<query(tdata,1,inf)<<endl;
  }

  return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值