POJ2528 Mayor‘s posters

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

题目链接

题意

一段正整数值域(每个位置初始为无值)可执行一个操作:
[ 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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值