题目大意: 在数轴上有 n n n 条线段,如果两条线段相交则称其连通,连通块数量的 K K K 次方称为该线段集合的贡献,求这 n n n 条线段的所有子集(一共 2 n 2^n 2n 个)的贡献之和。
题解
这个
K
K
K 次方不好处理,按套路用第二类斯特林数转化一下:
∑
P
∈
S
a
n
s
(
P
)
K
=
∑
i
=
1
K
i
!
S
(
K
,
i
)
∑
P
∈
S
C
a
n
s
(
P
)
i
\sum_{P\in S}ans(P)^K=\sum_{i=1}^Ki!S(K,i)\sum_{P\in S}C_{ans(P)}^i
P∈S∑ans(P)K=i=1∑Ki!S(K,i)P∈S∑Cans(P)i
令 f [ i ] [ j ] f[i][j] f[i][j] 表示子集中最右的端点在 i i i 位置时的 ∑ C a n s ( P ) j \sum C_{ans(P)}^j ∑Cans(P)j,将线段按左端点从小到大排序,考虑每条线段的贡献。
对于一条线段 [ l , r ] [l,r] [l,r],贡献有三类:
- 对于 i ∈ [ 1 , l ) i\in [1,l) i∈[1,l),加上 [ l , r ] [l,r] [l,r] 后连通块数会 + 1 +1 +1,用 C n m = C n − 1 m + C n − 1 m − 1 C_n^m=C_{n-1}^m+C_{n-1}^{m-1} Cnm=Cn−1m+Cn−1m−1 来求出新增的贡献,即用 f [ i ] [ j ] + f [ i ] [ j − 1 ] f[i][j]+f[i][j-1] f[i][j]+f[i][j−1] 来贡献 f [ r ] [ j ] f[r][j] f[r][j]。
- 对于 i ∈ [ l , r ] i\in [l,r] i∈[l,r],加上 [ l , r ] [l,r] [l,r] 后连通块数不变,直接用 f [ i ] [ j ] f[i][j] f[i][j] 来贡献 f [ r ] [ j ] f[r][j] f[r][j] 即可。
- 对于 i ∈ ( r , n ] i\in (r,n] i∈(r,n],注意到如果 f [ i ] [ j ] > 0 f[i][j]>0 f[i][j]>0,那么一定存在一条线段 [ x , y ] [x,y] [x,y] 满足 x ≤ l , y > r x\leq l,y>r x≤l,y>r,即 [ l , r ] [l,r] [l,r] 是被 [ x , y ] [x,y] [x,y] 包含的,那么 [ l , r ] [l,r] [l,r] 选不选无所谓,令 f [ i ] [ j ] f[i][j] f[i][j] 乘以 2 2 2 即可。
用线段树维护一下 f f f 就做完了,代码如下:
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define maxn 100010
#define mod 1000000007
int n,k;
struct seg{int l,r;}a[maxn];
bool cmp(seg x,seg y){return x.l<y.l;}
void add(int &x,int y){x=(x+y>=mod?x+y-mod:x+y);}
int add(int x){return x>=mod?x-mod:x;}
#define zuo ch[0]
#define you ch[1]
struct node{
int l,r,mid,c[11],lazy;node *ch[2];
node(int x,int y):l(x),r(y),mid(l+r>>1),lazy(1){
for(int i=0;i<=k;i++)c[i]=0;
if(x<y){
zuo=new node(l,mid);
you=new node(mid+1,r);
}else zuo=you=NULL;
}
void update(int x){
lazy=1ll*lazy*x%mod;
for(int i=0;i<=k;i++)c[i]=1ll*c[i]*x%mod;
}
void pushdown(){
if(lazy!=1){
if(zuo)zuo->update(lazy),you->update(lazy);
lazy=1;
}
}
void change(int x,int *w){
pushdown();
for(int i=0;i<=k;i++)add(c[i],w[i]);
if(l<r)ch[x>=mid+1]->change(x,w);
}
void change(int x,int y,int z){
pushdown();
if(l==x&&r==y)return update(z);
if(y<=mid)zuo->change(x,y,z);
else if(x>=mid+1)you->change(x,y,z);
else zuo->change(x,mid,z),you->change(mid+1,y,z);
for(int i=0;i<=k;i++)c[i]=add(zuo->c[i]+you->c[i]);
}
void ask(int x,int y,int *w){
pushdown();
if(l==x&&r==y){
for(int i=0;i<=k;i++)add(w[i],c[i]);
return;
}
if(y<=mid)zuo->ask(x,y,w);
else if(x>=mid+1)you->ask(x,y,w);
else zuo->ask(x,mid,w),you->ask(mid+1,y,w);
}
}*root=NULL;
int tmp[11],d[11];
int S[11][11],ans=0;
int main()
{
scanf("%d %d",&n,&k);
for(int i=1;i<=n;i++)scanf("%d %d",&a[i].l,&a[i].r);
sort(a+1,a+n+1,cmp);
root=new node(0,2*n);
tmp[0]=1;root->change(0,tmp);
for(int i=1;i<=n;i++){
for(int j=0;j<=k;j++)d[j]=0;root->ask(0,a[i].l-1,d);
tmp[0]=d[0];for(int i=1;i<=k;i++)tmp[i]=add(d[i]+d[i-1]);
for(int j=0;j<=k;j++)d[j]=0;root->ask(a[i].l,a[i].r,d);
for(int i=0;i<=k;i++)add(tmp[i],d[i]);
if(a[i].r<2*n)root->change(a[i].r+1,2*n,2);
root->change(a[i].r,tmp);
}
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]+1ll*j*S[i-1][j]%mod);
}
}
int fac=1;
for(int i=1;i<=k;i++){
fac=1ll*fac*i%mod;
add(ans,1ll*fac*S[k][i]%mod*root->c[i]%mod);
}
printf("%d",ans);
}