知识点:(动态开点)线段树、离散化
题意
一段正整数值域(每个位置初始为无值)可执行一个操作:
对
[
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;
}