Processing math: 30%

LOJ3267 Help Yourself

Help Yourself

Bessie 现在有 N 条在一条数轴上的线段,第 i 条线段覆盖了 [li,ri] 的所有实数。

定义一个线段集合的为所有至少被一条线段覆盖的实数。定义一个线段集合的复杂度为该集合并的联通块个数的 K 次方。

Bessie 现在想计算这 N 条线段的 2N 个子集的复杂度之和模 109+7。通常你的任务是帮 Bessie 进行计算,但是这次你是 Bessie,而且没人能帮你,帮帮你自己吧!

对于 100% 的数据,有 1N105, 2K10, 1li<ri2N

题解

http://jklover.hs-blog.cf/2020/06/06/Loj-3267-Help-Yourself/#more

第二类斯特林数 + 线段树优化 dp 转移.

USACO 怎么也有如此套路的题…

可以先将所有线段按照左端点从小到大排序,方便后续处理.

记一个非空集合 T 的线段形成的连通块数目为 c(T) ,则答案为 Tc(T)k .

朴素的 dp

dp(i,x,p) 表示考虑了前 i 条线段,选了的线段右端点最大值为 x ,有 p 个连通块的方案数.

这样做状态数为 O(n3) ,不太可行.

幂次展开为组合数

注意到 k​ 比较小,尝试将其展开为组合数的形式,

\begin{aligned} ans&=\sum_T c(T)^k \\ &=\sum_T\sum_{i=0}^k {k\brace i}\cdot i!\cdot \binom{c(T)}{i}\\ &=\sum_{i=0}^k{k\brace i}\cdot i!\cdot \sum_{T}\binom{c(T)}{i} \end{aligned}

当题目中要算 c^k 的贡献, c 大而 k 小,尤其是 c 是某种东西的数目时,可以尝试展开成组合数.

原来需要记录 c 的大小,最后算贡献,现在就将贡献摊在每个 c 增大的时候计算,只用记录 k 的大小.

于是我们在状态中就不需要记录连通块数目,而是在新产生一个连通块时,考虑它是否被选.

dp(i,x,p) 表示考虑了前 i 条线段,选了的线段右端点最大值为 x ,产生的连通块被选定了 p 个的方案数.

状态数从 O(n^3) 降到了 O(n^2k) ,还需进一步优化.

线段树优化转移

考虑转移的形式,假定当前在考虑第 i 条线段,其覆盖的区间为 [l,r] .

若不选这条线段,则每个 dp(i-1,x,p) 转移到 dp(i,x,p) .

若选了这条线段,则根据 x​ 的大小分情况讨论.

x<l​ ,此时会产生新的一个连通块, dp(i-1,x,p)​ 可以转移到 dp(i,r,p)​dp(i,r,p+1)​ .

l\le x\le r​ , dp(i-1,x,p)​ 可以转移到 dp(i,r,p)​ .

x>r , dp(i-1,x,p) 可以转移到 dp(i,x,p) .

不难发现,我们可以开 k+1 棵线段树来维护这个 dp 数组,第 p 棵线段树维护了当前所有的 dp(i,x,p) .

i通过for枚举,p通过k+1棵线段树维护,x通过线段树下标维护。

时间复杂度 O(nk\log n) .

CO int N=2e5+10;
struct Seg{
int sum[4*N],tag[4*N];
#define lc (x<<1)
#define rc (x<<1|1)
#define mid ((l+r)>>1)
IN void push_up(int x){
sum[x]=add(sum[lc],sum[rc]);
}
IN void put_tag(int x,int v){
sum[x]=mul(sum[x],v),tag[x]=mul(tag[x],v);
}
IN void push_down(int x){
if(tag[x]!=1){
put_tag(lc,tag[x]),put_tag(rc,tag[x]);
tag[x]=1;
}
}
void insert(int x,int l,int r,int p,int v){
if(l==r) {sum[x]=add(sum[x],v); return;}
push_down(x);
if(p<=mid) insert(lc,l,mid,p,v);
else insert(rc,mid+1,r,p,v);
push_up(x);
}
void modify(int x,int l,int r,int ql,int qr,int v){
if(ql>qr) return;
if(ql<=l and r<=qr) return put_tag(x,v);
push_down(x);
if(ql<=mid) modify(lc,l,mid,ql,qr,v);
if(qr>mid) modify(rc,mid+1,r,ql,qr,v);
push_up(x);
}
int query(int x,int l,int r,int ql,int qr){
if(ql>qr) return 0;
if(ql<=l and r<=qr) return sum[x];
push_down(x);
if(qr<=mid) return query(lc,l,mid,ql,qr);
if(ql>mid) return query(rc,mid+1,r,ql,qr);
return add(query(lc,l,mid,ql,qr),query(rc,mid+1,r,ql,qr));
}
#undef lc
#undef rc
#undef mid
}T[11];
pair<int,int> seg[N];
int S[11][11];
int main(){
int n=read<int>(),K=read<int>();
for(int i=1;i<=n;++i) read(seg[i].first),read(seg[i].second);
sort(seg+1,seg+n+1);
S[0][0]=1;
for(int i=1;i<=K;++i)for(int j=1;j<=i;++j)
S[i][j]=add(S[i-1][j-1],mul(S[i-1][j],j));
T[0].insert(1,0,2*n,0,1);
for(int i=1;i<=n;++i){
int l=seg[i].first,r=seg[i].second;
for(int p=K;p>=0;--p){
if(p<K) T[p+1].insert(1,0,2*n,r,T[p].query(1,0,2*n,0,l-1));
T[p].insert(1,0,2*n,r,T[p].query(1,0,2*n,0,r));
T[p].modify(1,0,2*n,r+1,2*n,2);
}
}
int ans=0,fac=1;
for(int p=0;p<=K;++p){
ans=add(ans,mul(T[p].sum[1],mul(S[K][p],fac)));
fac=mul(fac,p+1);
}
printf("%d\n",ans);
return 0;
}

posted on   autoint  阅读(173)  评论(0编辑  收藏  举报

导航

点击右上角即可分享
微信分享提示