【YbtOJ461】「数据结构优化 DP」区间覆盖(线段树优化DP)
- 给定数轴上\(n\)个区间和\(m\)个点。
- 求有多少种选择区间的方式,满足能覆盖所有点。
- \(n,m\le5\times10^5\)
线段树优化\(DP\)
考虑先把所有区间按左端点排序,然后维护\(f_{i,j}\)表示处理到第\(i\)个区间,最大右端点为\(j\)的概率。
假设第\(i\)个区间左端点右侧第一个点编号为\(l\),右端点右侧第一个点为\(r\)。
那么\(f_{i-1,j}(j\in[l-1\sim r-1])\)都可以转移到\(f_{i,r}\),\(f_{i-1,j}(j\in[r,m])\)都可以转移到对应的\(f_{i,j}\)。
其实也就是每次对\([l-1,r-1]\)区间求和然后对\(r\)单点修改,并对\([r,m]\)区间乘\(2\)。
代码:\(O(nlogn)\)
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 500000
#define X 1000000009
#define Inc(x,y) ((x+=(y))>=X&&(x-=X))
using namespace std;
int n,m,p[N+5],dc,dv[2*N+5],pos[2*N+5],f[2*N+5],g[2*N+5];
struct Il
{
int l,r;I Il(CI a=0,CI b=0):l(a),r(b){}
I bool operator < (Con Il& o) Con {return l<o.l;}
}s[2*N+5];
class FastIO
{
private:
#define FS 100000
#define tc() (A==B&&(B=(A=FI)+fread(FI,1,FS,stdin),A==B)?EOF:*A++)
#define D isdigit(c=tc())
char c,*A,*B,FI[FS];
public:
I FastIO() {A=B=FI;}
Tp I void read(Ty& x) {x=0;W(!D);W(x=(x<<3)+(x<<1)+(c&15),D);}
}F;
class SegmentTree
{
private:
#define PT CI l=0,CI r=m,CI rt=1
#define LT l,mid,rt<<1
#define RT mid+1,r,rt<<1|1
#define PU(x) (V[x]=(V[x<<1]+V[x<<1|1])%X)
#define PD(x) F[x]^1&&(T(x<<1,F[x]),T(x<<1|1,F[x]),F[x]=1)
#define T(x,v) (V[x]=1LL*V[x]*v%X,F[x]=1LL*F[x]*v%X)
int V[N<<2],F[N<<2];
public:
I void Init() {memset(F,1,sizeof(F));}
I void U(CI x,CI v,PT)//单点修改
{
if(l==r) return (void)Inc(V[rt],v);RI mid=l+r>>1;PD(rt);
x<=mid?U(x,v,LT):U(x,v,RT),PU(rt);
}
I void M(CI L,CI R,PT)//区间乘2
{
if(L<=l&&r<=R) return (void)T(rt,2);RI mid=l+r>>1;PD(rt);
L<=mid&&(M(L,R,LT),0),R>mid&&(M(L,R,RT),0),PU(rt);
}
I int Q(CI L,CI R,PT)//区间求和
{
if(L<=l&&r<=R) return V[rt];RI mid=l+r>>1;PD(rt);
return ((L<=mid?Q(L,R,LT):0)+(R>mid?Q(L,R,RT):0))%X;
}
}S;
int main()
{
freopen("xmasinterval.in","r",stdin),freopen("xmasinterval.out","w",stdout);
RI i;for(F.read(n),F.read(m),i=1;i<=n;++i)
F.read(s[i].l),F.read(s[i].r),dv[i]=s[i].l,dv[n+i]=++s[i].r;//改成左闭右开区间
for(sort(dv+1,dv+2*n+1),dc=unique(dv+1,dv+2*n+1)-dv-1,i=1;i<=n;++i)
s[i].l=lower_bound(dv+1,dv+dc+1,s[i].l)-dv,s[i].r=lower_bound(dv+1,dv+dc+1,s[i].r)-dv;//离散化
for(i=1;i<=m;++i) F.read(p[i]),p[i]=upper_bound(dv+1,dv+dc+1,p[i])-dv-1;
for(sort(p+1,p+m+1),p[m+1]=dc,i=m+1;i;--i) pos[p[i]]=i;for(i=dc;i;--i) !pos[i]&&(pos[i]=pos[i+1]);//求出每个位置右边第一个点编号
for(sort(s+1,s+n+1),S.Init(),S.U(0,1),i=1;i<=n;++i)//按左端点排序后枚举区间
S.U(pos[s[i].r]-1,S.Q(pos[s[i].l]-1,pos[s[i].r]-1)),pos[s[i].r]<=m&&(S.M(pos[s[i].r],m),0);//线段树优化DP
return printf("%d\n",S.Q(m,m)),0;//单点询问得出答案
}
待到再迷茫时回头望,所有脚印会发出光芒