[LuoguP6144][USACO20FEB]Help Yourself P(DP+组合数学+线段树)
题面
Bessie 现在有 N条在一条数轴上的线段,第 i条线段覆盖了 \([l_i,r_i](1 \leq l_i,r_i \leq 2N)\)的所有实数。定义一个线段集合的并为所有至少被一条线段覆盖的实数。定义一个线段集合的复杂度为该集合并的联通块个数的 K 次方。
Bessie现在想计算这N条线段的\(2^N\)个子集的复杂度之和模 \(10^9+7\)
分析
先把线段排序。
设\(dp_{i,j,t}\)表示前\(i\)条线段,覆盖到最右边的点为\(j\)的所有子集,每个子集的连通块个数的\(t\)次方之和。(相当于把题面中的复杂度改成了t次方,原因是下面要用二项式定理转移).
显然有初始值\(dp_{0,0,0}=1\),答案为\(\sum_{j=1}^{2n}dp_{n,j,K}\)
考虑添加第\(i\)条线段\([l_i,r_i]\)对答案的影响。
(1) 对于\(j<l_i\)的状态,加入\([l_i,r_i]\)后连通块个数会+1,最右边的点变为\(r_i\). 设\(dp_{i-1,j,t}\)对应的子集为\(S\),\(cnt(S)\)为子集的连通块个数。\(dp_{i,r_i,t}\)会增加的值为
(2)对于\(l_i \leq j \leq r_i\)的状态,加入\([l_i,r_i]\)后连通块个数不变,最右边的点变为\(r_i\). \(dp_{i,r_i,t}\)会增加的值为\(dp_{i-1,j,t}\)
(3)对于\(j>r_i\)的状态,加入\([l_i,r_i]\)后连通块个数不变,最右边的点也不变。但是子集的个数乘了2(每个子集都可以选或不选第\(i\)个区间)。因为每个子集的连通块个数不变,所以把\(dp_{i,j,t}\)乘2即可。
那么我们就可以用线段树维护DP转移。显然\(i\)这一维可以去掉,线段树的叶子节点\([j,j]\)维护一个\(K\)维向量代表\(dp_{i,j}\).
(1) 求\([0,l-1]\)的区间和,再按照上面的组合式计算出增加量,然后对\(r\)单点增加.
(2) 求\([l,r]\)的区间和,然后对\(r\)单点增加.
(3) 对区间\([r+1,n]\)区间乘2
注意所有查询操作要在修改操作前。那么维护一个支持单点加向量,区间数乘向量,查询区间向量和的线段树即可。
复杂度\(O(nK\log n)\)
代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define maxn 200000
#define maxk 10
#define mod 1000000007
using namespace std;
typedef long long ll;
int n,K;
struct seg{
int l;
int r;
friend bool operator < (seg p,seg q){
if(p.l==q.l) return p.r<q.r;
else return p.l<q.l;
}
}a[maxn+5];
ll C[maxk+5][maxk+5];
void ini(int m){
for(int i=0;i<=m;i++){
C[i][0]=C[i][i]=1;
for(int j=0;j<i;j++) C[i][j]=(C[i-1][j-1]+C[i-1][j])%mod;
}
}
struct val_type{//维护一个K维向量
ll a[maxk+5];
val_type(){
memset(a,0,sizeof(a));
}
ll & operator [] (const int i){
return a[i];
}
friend val_type operator + (val_type p,val_type q){
val_type ans;
for(int i=0;i<=K;i++) ans[i]=(p[i]+q[i])%mod;
return ans;
}
friend val_type operator * (val_type p,ll x){
val_type ans;
for(int i=0;i<=K;i++) ans[i]=p[i]*x%mod;
return ans;
}
void print(){
printf("debug:");
for(int i=0;i<=K;i++) printf("%lld ",a[i]);
printf("\n");
}
};
struct segment_tree{
struct node{
int l;
int r;
ll mtag;
val_type v;
}tree[maxn*4+5];
void push_up(int pos){
tree[pos].v=tree[pos<<1].v+tree[pos<<1|1].v;
}
void mul_tag(int pos,int v){
tree[pos].mtag=tree[pos].mtag*v%mod;
tree[pos].v=tree[pos].v*v;
}
void push_down(int pos){
if(tree[pos].mtag!=1){
mul_tag(pos<<1,tree[pos].mtag);
mul_tag(pos<<1|1,tree[pos].mtag);
tree[pos].mtag=1;
}
}
void build(int l,int r,int pos){
tree[pos].l=l;
tree[pos].r=r;
tree[pos].mtag=1;
if(l==r) return;
int mid=(l+r)>>1;
build(l,mid,pos<<1);
build(mid+1,r,pos<<1|1);
}
void add_point(int upos,val_type &uval,int pos){//单点加向量
if(tree[pos].l==tree[pos].r){
tree[pos].v=tree[pos].v+uval;
return;
}
push_down(pos);
int mid=(tree[pos].l+tree[pos].r)>>1;
if(upos<=mid) add_point(upos,uval,pos<<1);
else add_point(upos,uval,pos<<1|1);
push_up(pos);
}
void mul_seg(int L,int R,ll uval,int pos){//区间数乘
if(L<=tree[pos].l&&R>=tree[pos].r){
mul_tag(pos,uval);
return;
}
push_down(pos);
int mid=(tree[pos].l+tree[pos].r)>>1;
if(L<=mid) mul_seg(L,R,uval,pos<<1);
if(R>mid) mul_seg(L,R,uval,pos<<1|1);
push_up(pos);
}
val_type query(int L,int R,int pos){//查询区间向量和
if(L<=tree[pos].l&&R>=tree[pos].r){
return tree[pos].v;
}
push_down(pos);
int mid=(tree[pos].l+tree[pos].r)>>1;
val_type ans;
if(L<=mid) ans=ans+query(L,R,pos<<1);
if(R>mid) ans=ans+query(L,R,pos<<1|1);
return ans;
}
}T;
int main(){
scanf("%d %d",&n,&K);
ini(K);
for(int i=1;i<=n;i++) scanf("%d %d",&a[i].l,&a[i].r);
sort(a+1,a+1+n);
T.build(0,n*2,1);
val_type tmp;
tmp[0]=1;
T.add_point(0,tmp,1);//dp[0]初始化为1
for(int i=1;i<=n;i++){
int l=a[i].l,r=a[i].r;
val_type last=T.query(0,l-1,1);
// last.print();
val_type now;
for(int i=0;i<=K;i++){
for(int j=0;j<=i;j++){
now[i]+=last[j]*C[i][j]%mod;
now[i]%=mod;
}
}
now=now+T.query(l,r,1);
// now.print();
T.add_point(r,now,1);
if(r!=n*2) T.mul_seg(r+1,n*2,2,1);
// T.tree[1].v.print();
}
printf("%lld\n",T.tree[1].v[K]);
}