LOJ3267 Help Yourself
Help Yourself
Bessie 现在有 \(N\) 条在一条数轴上的线段,第 \(i\) 条线段覆盖了 \([l_i,r_i]\) 的所有实数。
定义一个线段集合的并为所有至少被一条线段覆盖的实数。定义一个线段集合的复杂度为该集合并的联通块个数的 \(K\) 次方。
Bessie 现在想计算这 \(N\) 条线段的 \(2^N\) 个子集的复杂度之和模 \(10^9+7\)。通常你的任务是帮 Bessie 进行计算,但是这次你是 Bessie,而且没人能帮你,帮帮你自己吧!
对于 \(100\%\) 的数据,有 \(1\le N \le 10^5,~2\le K\le 10,~1\le l_i<r_i\le 2N\)。
题解
http://jklover.hs-blog.cf/2020/06/06/Loj-3267-Help-Yourself/#more
第二类斯特林数 + 线段树优化 dp 转移.
USACO 怎么也有如此套路的题…
可以先将所有线段按照左端点从小到大排序,方便后续处理.
记一个非空集合 \(T\) 的线段形成的连通块数目为 \(c(T)\) ,则答案为 \(\sum_T c(T)^k\) .
朴素的 dp
设 \(dp(i,x,p)\) 表示考虑了前 \(i\) 条线段,选了的线段右端点最大值为 \(x\) ,有 \(p\) 个连通块的方案数.
这样做状态数为 \(O(n^3)\) ,不太可行.
幂次展开为组合数
注意到 \(k\) 比较小,尝试将其展开为组合数的形式,
当题目中要算 \(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; }
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 智能桌面机器人:用.NET IoT库控制舵机并多方法播放表情
· Linux glibc自带哈希表的用例及性能测试
· 深入理解 Mybatis 分库分表执行原理
· 如何打造一个高并发系统?
· .NET Core GC压缩(compact_phase)底层原理浅谈
· 开发者新选择:用DeepSeek实现Cursor级智能编程的免费方案
· Tinyfox 发生重大改版
· 独立开发经验谈:如何通过 Docker 让潜在客户快速体验你的系统
· 小米CR6606,CR6608,CR6609 启用SSH和刷入OpenWRT 23.05.5
· 近期最值得关注的AI技术报告与Agent综述!
2019-06-17 [POI2012]RAN-Rendezvous